roojs-ui.js
[roojs1] / roojs-bootstrap-debug.js
index 1606bdf..0ab5de6 100644 (file)
@@ -6,13 +6,13 @@
 Roo.bootstrap.version = (
         function() {
                 var ret=3;
-                Roo.each(document.styleSheets[0], function(s) {
-                    if (s.href.match(/css-bootstrap4/)) {
+                Roo.each(document.styleSheets, function(s) {
+                    if ( s.href  && s.href.match(/css-bootstrap4/)) {
                         ret=4;
                     }
                 });
         return ret;
-})();/*
+})(); /*
  * - LGPL
  *
  * base class for bootstrap elements.
@@ -1072,10 +1072,12 @@ Roo.extend(Roo.bootstrap.Column, Roo.bootstrap.Component,  {
             }
             
             if (!settings[size]) { // 0 = hidden
-                cfg.cls += ' hidden-' + size;
+                cfg.cls += ' hidden-' + size + ' hidden' + size + '-down';;
                 return;
             }
-            cfg.cls += ' col-' + size + '-' + settings[size];
+            cfg.cls += ' col-' + size + '-' + settings[size] + (
+                size == 'xs' ? (' col-' + settings[size] ) : '' // bs4 col-{num} replaces col-xs
+            );
             
         });
         
@@ -2060,13 +2062,13 @@ Roo.bootstrap.Menu = function(config){
     this.addEvents({
         /**
          * @event beforeshow
-         * Fires before this menu is displayed
+         * Fires before this menu is displayed (return false to block)
          * @param {Roo.menu.Menu} this
          */
         beforeshow : true,
         /**
          * @event beforehide
-         * Fires before this menu is hidden
+         * Fires before this menu is hidden (return false to block)
          * @param {Roo.menu.Menu} this
          */
         beforehide : true,
@@ -2268,7 +2270,7 @@ Roo.extend(Roo.bootstrap.Menu, Roo.bootstrap.Component,  {
     isVisible : function(){
         return !this.hidden;
     },
-     onMouseOut : function(e){
+    onMouseOut : function(e){
         var t  = this.findTargetItem(e);
         
         //if(t ){
@@ -2288,12 +2290,17 @@ Roo.extend(Roo.bootstrap.Menu, Roo.bootstrap.Component,  {
      * the element (defaults to this.defaultAlign)
      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
      */
-    show : function(el, pos, parentMenu){
-        this.parentMenu = parentMenu;
+    show : function(el, pos, parentMenu)
+    {
+        if (false === this.fireEvent("beforeshow", this)) {
+           Roo.log("show canceled");
+           return;
+       }
+       this.parentMenu = parentMenu;
         if(!this.el){
             this.render();
         }
-        this.fireEvent("beforeshow", this);
+        
         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
     },
      /**
@@ -2356,10 +2363,13 @@ Roo.extend(Roo.bootstrap.Menu, Roo.bootstrap.Component,  {
      */
     hide : function(deep)
     {
-        
+        if (false === this.fireEvent("beforehide", this)) {
+           Roo.log("hide canceled");
+           return;
+       }
         this.hideMenuItems();
         if(this.el && this.isVisible()){
-            this.fireEvent("beforehide", this);
+           
             if(this.activeItem){
                 this.activeItem.deactivate();
                 this.activeItem = null;
@@ -2426,16 +2436,11 @@ Roo.extend(Roo.bootstrap.Menu, Roo.bootstrap.Component,  {
         if (!this.el) { 
             return;
         }
-        //$(backdrop).remove()
+        
         this.el.select('.open',true).each(function(aa) {
             
             aa.removeClass('open');
-          //var parent = getParent($(this))
-          //var relatedTarget = { relatedTarget: this }
-          
-           //$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
-          //if (e.isDefaultPrevented()) return
-           //$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+         
         });
     },
     addxtypeChild : function (tree, cntr) {
@@ -2646,6 +2651,8 @@ Roo.extend(Roo.bootstrap.MenuSeparator, Roo.bootstrap.Component,  {
  * @cfg {Boolean} animate default true
  * @cfg {Boolean} allow_close default true
  * @cfg {Boolean} fitwindow default false
+ * @cfg {Number} width fixed width - usefull for chrome extension only really.
+ * @cfg {Number} height fixed height - usefull for chrome extension only really.
  * @cfg {String} size (sm|lg) default empty
  * @cfg {Number} max_width set the max width of modal
  *
@@ -2796,8 +2803,9 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
 
     getAutoCreate : function()
     {
+        // we will default to modal-body-overflow - might need to remove or make optional later.
         var bdy = {
-                cls : 'modal-body',
+                cls : 'modal-body enable-modal-body-overflow ', 
                 html : this.html || ''
         };
 
@@ -2810,7 +2818,7 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
         if(this.specificTitle){
             title = this.title;
 
-        };
+        }
 
         var header = [];
         if (this.allow_close && Roo.bootstrap.version == 3) {
@@ -2908,6 +2916,7 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
 
 
     },
+  
 
     resize : function()
     {
@@ -2917,9 +2926,11 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
         );
         
         if (this.fitwindow) {
+            
+           
             this.setSize(
                 this.width || Roo.lib.Dom.getViewportWidth(true) - 30,
-                this.height || Roo.lib.Dom.getViewportHeight(true) - 60
+                this.height || Roo.lib.Dom.getViewportHeight(true) // catering margin-top 30 margin-bottom 30
             );
             return;
         }
@@ -2980,7 +2991,7 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
         Roo.get(document.body).addClass('modal-open');
  
         if(this.animate){  // element has 'fade'  - so stuff happens after .3s ?- not sure why the delay?
-            var _this = this;
+            
             (function(){
                 this.el.addClass('show');
                 this.el.addClass('in');
@@ -3082,22 +3093,18 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component,  {
     {
         //this.el.select('.modal-footer').()
     },
-    diff : false,
 
     resizeTo: function(w,h)
     {
-        // skip.. ?? why??
-
         this.dialogEl.setWidth(w);
-        if (this.diff === false) {
-            this.diff = this.dialogEl.getHeight() - this.bodyEl.getHeight();
-        }
-
-        this.bodyEl.setHeight(h - this.diff);
+        
+        var diff = this.headerEl.getHeight() + this.footerEl.getHeight() + 60; // dialog margin-bottom: 30  
 
+        this.bodyEl.setHeight(h - diff);
+        
         this.fireEvent('resize', this);
-
     },
+    
     setContentSize  : function(w, h)
     {
 
@@ -3876,46 +3883,7 @@ Roo.extend(Roo.bootstrap.Navbar, Roo.bootstrap.Component,  {
     initEvents :function ()
     {
         //Roo.log(this.el.select('.navbar-toggle',true));
-        this.el.select('.navbar-toggle',true).on('click', function() {
-            if(this.fireEvent('beforetoggle', this) !== false){
-                var ce = this.el.select('.navbar-collapse',true).first();
-                ce.toggleClass('in'); // old...
-                if (ce.hasClass('collapse')) {
-                    // show it...
-                    ce.removeClass('collapse');
-                    ce.addClass('show');
-                    var h = ce.getHeight();
-                    Roo.log(h);
-                    ce.removeClass('show');
-                    // at this point we should be able to see it..
-                    ce.addClass('collapsing');
-                    
-                    ce.setHeight(0); // resize it ...
-                    ce.on('transitionend', function() {
-                        Roo.log('done transition');
-                        ce.removeClass('collapsing');
-                        ce.addClass('show');
-                        ce.removeClass('collapse');
-
-                        ce.dom.style.height = '';
-                    }, this, { single: true} );
-                    ce.setHeight(h);
-                    
-                } else {
-                    ce.setHeight(ce.getHeight());
-                    ce.removeClass('show');
-                    ce.addClass('collapsing');
-                    
-                    ce.on('transitionend', function() {
-                        ce.dom.style.height = '';
-                        ce.removeClass('collapsing');
-                        ce.addClass('collapse');
-                    }, this, { single: true} );
-                    ce.setHeight(0);
-                }
-            }
-            
-        }, this);
+        this.el.select('.navbar-toggle',true).on('click', this.onToggle , this);
         
         var mark = {
             tag: "div",
@@ -3937,7 +3905,7 @@ Roo.extend(Roo.bootstrap.Navbar, Roo.bootstrap.Component,  {
     
     getChildContainer : function()
     {
-        if (this.el.select('.collapse').getCount()) {
+        if (this.el && this.el.select('.collapse').getCount()) {
             return this.el.select('.collapse',true).first();
         }
         
@@ -3952,8 +3920,80 @@ Roo.extend(Roo.bootstrap.Navbar, Roo.bootstrap.Component,  {
     unmask : function()
     {
         this.maskEl.hide();
-    } 
+    },
+    onToggle : function()
+    {
+        
+        if(this.fireEvent('beforetoggle', this) === false){
+            return;
+        }
+        var ce = this.el.select('.navbar-collapse',true).first();
+      
+        if (!ce.hasClass('show')) {
+           this.expand();
+        } else {
+            this.collapse();
+        }
+        
+        
     
+    },
+    /**
+     * Expand the navbar pulldown 
+     */
+    expand : function ()
+    {
+       
+        var ce = this.el.select('.navbar-collapse',true).first();
+        if (ce.hasClass('collapsing')) {
+            return;
+        }
+        ce.dom.style.height = '';
+               // show it...
+        ce.addClass('in'); // old...
+        ce.removeClass('collapse');
+        ce.addClass('show');
+        var h = ce.getHeight();
+        Roo.log(h);
+        ce.removeClass('show');
+        // at this point we should be able to see it..
+        ce.addClass('collapsing');
+        
+        ce.setHeight(0); // resize it ...
+        ce.on('transitionend', function() {
+            //Roo.log('done transition');
+            ce.removeClass('collapsing');
+            ce.addClass('show');
+            ce.removeClass('collapse');
+
+            ce.dom.style.height = '';
+        }, this, { single: true} );
+        ce.setHeight(h);
+        ce.dom.scrollTop = 0;
+    },
+    /**
+     * Collapse the navbar pulldown 
+     */
+    collapse : function()
+    {
+         var ce = this.el.select('.navbar-collapse',true).first();
+       
+        if (ce.hasClass('collapsing') || ce.hasClass('collapse') ) {
+            // it's collapsed or collapsing..
+            return;
+        }
+        ce.removeClass('in'); // old...
+        ce.setHeight(ce.getHeight());
+        ce.removeClass('show');
+        ce.addClass('collapsing');
+        
+        ce.on('transitionend', function() {
+            ce.dom.style.height = '';
+            ce.removeClass('collapsing');
+            ce.addClass('collapse');
+        }, this, { single: true} );
+        ce.setHeight(0);
+    }
     
     
     
@@ -4020,7 +4060,7 @@ Roo.extend(Roo.bootstrap.NavSimplebar, Roo.bootstrap.Navbar,  {
         
         var cfg = {
             tag : this.tag || 'div',
-            cls : 'navbar navbar-expand-lg'
+            cls : 'navbar roo-navbar-simple' //navbar-expand-lg ??
         };
        if (['light','white'].indexOf(this.weight) > -1) {
            cfg.cls += ['light','white'].indexOf(this.weight) > -1 ? ' navbar-light' : ' navbar-dark';
@@ -4034,20 +4074,23 @@ Roo.extend(Roo.bootstrap.NavSimplebar, Roo.bootstrap.Navbar,  {
        
        // i'm not actually sure these are really used - normally we add a navGroup to a navbar
        
-       if (Roo.bootstrap.version == 4) {
+       if (Roo.bootstrap.version == 4 && this.xtype == 'NavSimplebar') {
            return cfg;
        }
        
+       
+    
+       
         cfg.cn = [
             {
-                cls: 'nav',
+                cls: 'nav nav-' + this.xtype,
                 tag : 'ul'
             }
         ];
         
          
         this.type = this.type || 'nav';
-        if (['tabs','pills'].indexOf(this.type)!==-1) {
+        if (['tabs','pills'].indexOf(this.type) != -1) {
             cfg.cn[0].cls += ' nav-' + this.type
         
         
@@ -4061,7 +4104,7 @@ Roo.extend(Roo.bootstrap.NavSimplebar, Roo.bootstrap.Navbar,  {
         
         
         
-        if (['stacked','justified'].indexOf(this.arrangement)!==-1) {
+        if (['stacked','justified'].indexOf(this.arrangement) != -1) {
             cfg.cn[0].cls += ' nav-' + this.arrangement;
         }
         
@@ -4182,12 +4225,18 @@ Roo.extend(Roo.bootstrap.NavHeaderbar, Roo.bootstrap.NavSimplebar,  {
         
         cn.push({
             tag: 'div',
-            cls: 'collapse navbar-collapse',
+            cls: Roo.bootstrap.version == 4  ? 'nav flex-row roo-navbar-collapse' : 'collapse navbar-collapse roo-navbar-collapse',
             cn : []
         });
         
         cfg.cls += this.inverse ? ' navbar-inverse navbar-dark bg-dark' : ' navbar-default';
         
+        if (['light','white'].indexOf(this.weight) > -1) {
+           cfg.cls += ['light','white'].indexOf(this.weight) > -1 ? ' navbar-light' : ' navbar-dark';
+       }
+       cfg.cls += ' bg-' + this.weight;
+        
+        
         if (['fixed-top','fixed-bottom','static-top'].indexOf(this.position)>-1) {
             cfg.cls += ' navbar-' + this.position + ' ' + this.position ;
             
@@ -4365,11 +4414,18 @@ Roo.extend(Roo.bootstrap.NavGroup, Roo.bootstrap.Component,  {
             cls: 'nav' 
         };
         if (Roo.bootstrap.version == 4) {
-           if (this.type == 'pills') {
-               cfg.cls = ' nav-pills';
+           if (['tabs','pills'].indexOf(this.type) != -1) {
+               cfg.cls += ' nav-' + this.type; 
+           } else {
+               // trying to remove so header bar can right align top?
+               if (this.parent() && this.parent().xtype != 'NavHeaderbar') {
+                   // do not use on header bar... 
+                   cfg.cls += ' navbar-nav';
+               }
            }
+           
        } else {
-           if (['tabs','pills'].indexOf(this.type)!==-1) {
+           if (['tabs','pills'].indexOf(this.type) != -1) {
                cfg.cls += ' nav-' + this.type
            } else {
                if (this.type !== 'nav') {
@@ -4393,7 +4449,7 @@ Roo.extend(Roo.bootstrap.NavGroup, Roo.bootstrap.Component,  {
                 tag: 'form',
                 cls: 'navbar-form form-inline'
             };
-            
+            //nav navbar-right ml-md-auto
             if (this.align === 'right') {
                 cfg.cls += ' navbar-right ml-md-auto';
             } else {
@@ -4607,6 +4663,8 @@ Roo.apply(Roo.bootstrap.NavGroup, {
  * @extends Roo.bootstrap.Component
  * Bootstrap Navbar.NavItem class
  * @cfg {String} href  link to
+ * @cfg {String} button_weight (default | primary | secondary | success | info | warning | danger | link ) default none
+
  * @cfg {String} html content of button
  * @cfg {String} badge text inside badge
  * @cfg {String} badgecls (bg-green|bg-red|bg-yellow)the extra classes for the badge
@@ -4672,13 +4730,16 @@ Roo.extend(Roo.bootstrap.NavItem, Roo.bootstrap.Component,  {
     disabled : false,
     animateRef : false,
     was_active : false,
+    button_weight : '',
+    button_outline : false,
+    
+    navLink: false,
     
     getAutoCreate : function(){
          
         var cfg = {
             tag: this.tag,
             cls: 'nav-item'
-            
         };
         
         if (this.active) {
@@ -4687,6 +4748,27 @@ Roo.extend(Roo.bootstrap.NavItem, Roo.bootstrap.Component,  {
         if (this.disabled) {
             cfg.cls += ' disabled';
         }
+       
+       // BS4 only?
+       if (this.button_weight.length) {
+           cfg.tag = this.href ? 'a' : 'button';
+           cfg.html = this.html || '';
+           cfg.cls += ' btn btn' + (this.button_outline ? '-outline' : '') + '-' + this.button_weight;
+           if (this.href) {
+               cfg.href = this.href;
+           }
+           if (this.fa) {
+                cfg.html = '<i class="fa fas fa-'+this.fa+'"></i> <span>' + this.html + '</span>';
+            }
+           
+           // menu .. should add dropdown-menu class - so no need for carat..
+           
+           if (this.badge !== '') {
+                 
+                cfg.html += ' <span class="badge badge-secondary">' + this.badge + '</span>';
+            }
+           return cfg;
+       }
         
         if (this.href || this.html || this.glyphicon || this.icon || this.fa) {
             cfg.cn = [
@@ -4700,10 +4782,10 @@ Roo.extend(Roo.bootstrap.NavItem, Roo.bootstrap.Component,  {
                cfg.cn[0].cls = 'nav-link';
            }
             if (this.icon) {
-                cfg.cn[0].html = '<i class="'+this.icon+'"></i> <span>' + cfg.cn[0].html + '</span>'
+                cfg.cn[0].html = '<i class="'+this.icon+'"></i> <span>' + cfg.cn[0].html + '</span>';
             }
            if (this.fa) {
-                cfg.cn[0].html = '<i class="fa fas fa-'+this.fa+'"></i> <span>' + cfg.cn[0].html + '</span>'
+                cfg.cn[0].html = '<i class="fa fas fa-'+this.fa+'"></i> <span>' + cfg.cn[0].html + '</span>';
             }
             if(this.glyphicon) {
                 cfg.cn[0].html = '<span class="glyphicon glyphicon-' + this.glyphicon + '"></span> '  + cfg.cn[0].html;
@@ -4732,7 +4814,9 @@ Roo.extend(Roo.bootstrap.NavItem, Roo.bootstrap.Component,  {
            this.tag = 'div';
        }
        
-        return Roo.bootstrap.NavItem.superclass.onRender.call(this, ct, position);
+        var ret = Roo.bootstrap.NavItem.superclass.onRender.call(this, ct, position);
+       this.navLink = this.el.select('.nav-link',true).first();
+       return ret;
     },
       
     
@@ -4816,7 +4900,7 @@ Roo.extend(Roo.bootstrap.NavItem, Roo.bootstrap.Component,  {
         // if parent is a navbarheader....- and link is probably a '#' page ref.. then remove the expanded menu.
         if (p.parentType == 'NavHeaderbar' && !this.menu) {
             // remove the collapsed menu expand...
-            p.parent().el.select('.navbar-collapse',true).removeClass('in');  
+            p.parent().el.select('.roo-navbar-collapse',true).removeClass('in');  
         }
     },
     
@@ -4837,8 +4921,14 @@ Roo.extend(Roo.bootstrap.NavItem, Roo.bootstrap.Component,  {
         
         if (!state ) {
             this.el.removeClass('active');
+           this.navLink ? this.navLink.removeClass('active') : false;
         } else if (!this.el.hasClass('active')) {
+           
             this.el.addClass('active');
+           if (Roo.bootstrap.version == 4 && this.navLink ) {
+               this.navLink.addClass('active');
+           }
+           
         }
         if (fire) {
             this.fireEvent('changed', this, state);
@@ -5084,7 +5174,7 @@ Roo.extend(Roo.bootstrap.NavSidebarItem, Roo.bootstrap.NavItem,  {
             e.preventDefault();
         }
         
-        this.fireEvent('click', this);
+        this.fireEvent('click', this, e);
     },
     
     disable : function()
@@ -6731,8 +6821,10 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
                 c.html = '<i class="glyphicon"></i>' + c.html;
             }
             
+            // could use BS4 hidden-..-down 
+            
             if(typeof(config.lgHeader) != 'undefined'){
-                hh += '<span class="hidden-xs hidden-sm hidden-md">' + config.lgHeader + '</span>';
+                hh += '<span class="hidden-xs hidden-sm hidden-md ">' + config.lgHeader + '</span>';
             }
             
             if(typeof(config.mdHeader) != 'undefined'){
@@ -6789,14 +6881,18 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
                 if(typeof(config[size]) == 'undefined'){
                     return;
                 }
-                
+                 
                 if (!config[size]) { // 0 = hidden
-                    c.cls += ' hidden-' + size;
+                    // BS 4 '0' is treated as hide that column and below.
+                    c.cls += ' hidden-' + size + ' hidden' + size + '-down';
                     return;
                 }
                 
-                c.cls += ' col-' + size + '-' + config[size];
-
+                c.cls += ' col-' + size + '-' + config[size] + (
+                    size == 'xs' ? (' col-' + config[size] ) : '' // bs4 col-{num} replaces col-xs
+                );
+                
+                
             });
             
             header.cn.push(c)
@@ -7102,12 +7198,18 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component,  {
                     return;
                 }
                 
+                
+                  
                 if (!config[size]) { // 0 = hidden
-                    td.cls += ' hidden-' + size;
+                    // BS 4 '0' is treated as hide that column and below.
+                    td.cls += ' hidden-' + size + ' hidden' + size + '-down';
                     return;
                 }
                 
-                td.cls += ' col-' + size + '-' + config[size];
+                td.cls += ' col-' + size + '-' + config[size] + (
+                    size == 'xs' ? (' col-' +   config[size] ) : '' // bs4 col-{num} replaces col-xs
+                );
+                 
 
             });
             
@@ -7862,7 +7964,8 @@ Roo.extend(Roo.form.Action.Submit, Roo.form.Action, {
                 url:this.getUrl(!isPost),
                 method: method,
                 params:isPost ? this.getParams() : null,
-                isUpload: this.form.fileUpload
+                isUpload: this.form.fileUpload,
+                formData : this.form.formData
             }));
             
             this.uploadProgress();
@@ -8991,12 +9094,12 @@ Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
     
        
     /**
-     * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid")
+     * @cfg {String} invalidClass DEPRICATED - code uses BS4 - is-valid / is-invalid
      */
     invalidClass : "has-warning",
     
     /**
-     * @cfg {String} validClass The CSS class to use when marking a field valid (defaults to "x-form-invalid")
+     * @cfg {String} validClass DEPRICATED - code uses BS4 - is-valid / is-invalid
      */
     validClass : "has-success",
     
@@ -9748,8 +9851,8 @@ Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
             return;
         }
         
-     
-        this.el.removeClass(this.invalidClass);
+        
+        this.el.removeClass([this.invalidClass, 'is-invalid']);
         
         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
             
@@ -9779,7 +9882,8 @@ Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
         }
         
         this.el.removeClass([this.invalidClass, this.validClass]);
-        
+        this.inputEl().removeClass(['is-valid', 'is-invalid']);
+
         var feedback = this.el.select('.form-control-feedback', true).first();
             
         if(feedback){
@@ -9798,9 +9902,12 @@ Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
         if(this.allowBlank && !this.getRawValue().length){
             return;
         }
-        
-        this.el.addClass(this.validClass);
-        
+        if (Roo.bootstrap.version == 3) {
+            this.el.addClass(this.validClass);
+        } else {
+            this.inputEl().addClass('is-valid');
+        }
+
         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank && (this.getValue().length || this.forceFeedback)){
             
             var feedback = this.el.select('.form-control-feedback', true).first();
@@ -9826,11 +9933,13 @@ Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
         }
         
         this.el.removeClass([this.invalidClass, this.validClass]);
+        this.inputEl().removeClass(['is-valid', 'is-invalid']);
         
         var feedback = this.el.select('.form-control-feedback', true).first();
             
         if(feedback){
-            this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
+            this.el.select('.form-control-feedback', true).first().removeClass(
+                    [this.invalidFeedbackClass, this.validFeedbackClass]);
         }
 
         if(this.disabled){
@@ -9845,8 +9954,13 @@ Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
             this.indicator.removeClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
             this.indicator.addClass('visible');
         }
+        if (Roo.bootstrap.version == 3) {
+            this.el.addClass(this.invalidClass);
+        } else {
+            this.inputEl().addClass('is-invalid');
+        }
+        
         
-        this.el.addClass(this.invalidClass);
         
         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
             
@@ -10193,9 +10307,9 @@ Roo.extend(Roo.bootstrap.TextArea, Roo.bootstrap.Input,  {
         if(label && icon){
             icon.remove();
         }
-        
-        this.el.removeClass(this.invalidClass);
-        
+        this.el.removeClass( this.validClass);
+        this.inputEl().removeClass('is-invalid');
+         
         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
             
             var feedback = this.el.select('.form-control-feedback', true).first();
@@ -10219,6 +10333,7 @@ Roo.extend(Roo.bootstrap.TextArea, Roo.bootstrap.Input,  {
         }
         
         this.el.removeClass([this.invalidClass, this.validClass]);
+        this.inputEl().removeClass(['is-valid', 'is-invalid']);
         
         var feedback = this.el.select('.form-control-feedback', true).first();
             
@@ -10236,8 +10351,12 @@ Roo.extend(Roo.bootstrap.TextArea, Roo.bootstrap.Input,  {
         if(label && icon){
             icon.remove();
         }
+        if (Roo.bootstrap.version == 3) {
+            this.el.addClass(this.validClass);
+        } else {
+            this.inputEl().addClass('is-valid');
+        }
         
-        this.el.addClass(this.validClass);
         
         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank && (this.getValue().length || this.forceFeedback)){
             
@@ -10264,6 +10383,7 @@ Roo.extend(Roo.bootstrap.TextArea, Roo.bootstrap.Input,  {
         }
         
         this.el.removeClass([this.invalidClass, this.validClass]);
+        this.inputEl().removeClass(['is-valid', 'is-invalid']);
         
         var feedback = this.el.select('.form-control-feedback', true).first();
             
@@ -10286,9 +10406,14 @@ Roo.extend(Roo.bootstrap.TextArea, Roo.bootstrap.Input,  {
                 style : 'margin-right:5px;'
             }, label, true);
         }
-
-        this.el.addClass(this.invalidClass);
         
+        if (Roo.bootstrap.version == 3) {
+            this.el.addClass(this.invalidClass);
+        } else {
+            this.inputEl().addClass('is-invalid');
+        }
+        
+        // fixme ... this may be depricated need to test..
         if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
             
             var feedback = this.el.select('.form-control-feedback', true).first();
@@ -10333,7 +10458,7 @@ trigger.applyTo('my-field');
  * {@link Roo.bootstrap.DateField} and {@link Roo.bootstrap.ComboBox} are perfect examples of this.
  * @cfg {String} triggerClass An additional CSS class used to style the trigger button.  The trigger will always get the
  * class 'x-form-trigger' by default and triggerClass will be <b>appended</b> if specified.
- * @cfg {String} caret (search|calendar) a fontawesome for the trigger icon see http://fortawesome.github.io/Font-Awesome/icons/
+ * @cfg {String} caret (search|calendar) BS3 only - carat fa name
 
  * @constructor
  * Create a new TriggerField.
@@ -10541,7 +10666,7 @@ Roo.extend(Roo.bootstrap.TriggerField, Roo.bootstrap.Input,  {
                 tag :'span',
                 cls : 'input-group-addon input-group-append input-group-text btn dropdown-toggle',
                 cn : [
-                    caret,
+                    Roo.bootstrap.version == 3 ? caret : '',
                     {
                         tag: 'span',
                         cls: 'combobox-clear',
@@ -11700,6 +11825,16 @@ Roo.extend(Roo.data.Store, Roo.util.Observable, {
         var r = this.reader.readRecords(o);
         this.loadRecords(r, {add: append}, true);
     },
+    
+     /**
+     * using 'cn' the nested child reader read the child array into it's child stores.
+     * @param {Object} rec The record with a 'children array
+     */
+    loadDataFromChildren : function(rec)
+    {
+        this.loadData(this.reader.toLoadData(rec));
+    },
+    
 
     /**
      * Gets the number of cached records.
@@ -12004,14 +12139,16 @@ Roo.extend(Roo.data.Store, Roo.util.Observable, {
  * Small helper class to make creating Stores from Array data easier.
  * @cfg {Number} id The array index of the record id. Leave blank to auto generate ids.
  * @cfg {Array} fields An array of field definition objects, or field name strings.
+ * @cfg {Object} an existing reader (eg. copied from another store)
  * @cfg {Array} data The multi-dimensional array of data
  * @constructor
  * @param {Object} config
  */
-Roo.data.SimpleStore = function(config){
+Roo.data.SimpleStore = function(config)
+{
     Roo.data.SimpleStore.superclass.constructor.call(this, {
         isLocal : true,
-        reader: new Roo.data.ArrayReader({
+        reader: typeof(config.reader) != 'undefined' ? config.reader : new Roo.data.ArrayReader({
                 id: config.id
             },
             Roo.data.Record.create(config.fields)
@@ -12188,6 +12325,9 @@ Roo.data.DataReader = function(meta, recordType){
 };
 
 Roo.data.DataReader.prototype = {
+    
+    
+    readerType : 'Data',
      /**
      * Create an empty record
      * @param {Object} data (optional) - overlay some values
@@ -12208,6 +12348,7 @@ Roo.data.DataReader.prototype = {
         return new this.recordType(Roo.apply(da, d));
     }
     
+    
 };/*
  * Based on:
  * Ext JS Library 1.1.1
@@ -12315,7 +12456,7 @@ Roo.extend(Roo.data.MemoryProxy, Roo.data.DataProxy, {
         params = params || {};
         var result;
         try {
-            result = reader.readRecords(this.data);
+            result = reader.readRecords(params.data ? params.data :this.data);
         }catch(e){
             this.fireEvent("loadexception", this, arg, null, e);
             callback.call(scope, null, arg, false);
@@ -12746,6 +12887,8 @@ Roo.data.JsonReader = function(meta, recordType){
 };
 Roo.extend(Roo.data.JsonReader, Roo.data.DataReader, {
     
+    readerType : 'Json',
+    
     /**
      * @prop {Boolean} metaFromRemote  - if the meta data was loaded from the remote source.
      * Used by Store query builder to append _requestMeta to params.
@@ -12887,6 +13030,14 @@ Roo.extend(Roo.data.JsonReader, Roo.data.DataReader, {
             records : records,
             totalRecords : totalRecords
         };
+    },
+    // used when loading children.. @see loadDataFromChildren
+    toLoadData: function(rec)
+    {
+       // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
+       var data = typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
+       return { data : data, total : data.length };
+       
     }
 });/*
  * Based on:
@@ -12922,51 +13073,68 @@ var myReader = new Roo.data.ArrayReader({
  * <pre><code>
 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
   </code></pre>
- * @cfg {String} id (optional) The subscript within row Array that provides an ID for the Record
  * @constructor
  * Create a new JsonReader
  * @param {Object} meta Metadata configuration options.
- * @param {Object} recordType Either an Array of field definition objects
+ * @param {Object|Array} recordType Either an Array of field definition objects
+ * 
+ * @cfg {Array} fields Array of field definition objects
+ * @cfg {String} id Name of the property within a row object that contains a record identifier value.
  * as specified to {@link Roo.data.Record#create},
  * or an {@link Roo.data.Record} object
+ *
+ * 
  * created using {@link Roo.data.Record#create}.
  */
-Roo.data.ArrayReader = function(meta, recordType){
-    Roo.data.ArrayReader.superclass.constructor.call(this, meta, recordType);
+Roo.data.ArrayReader = function(meta, recordType)
+{    
+    Roo.data.ArrayReader.superclass.constructor.call(this, meta, recordType||meta.fields);
 };
 
 Roo.extend(Roo.data.ArrayReader, Roo.data.JsonReader, {
-    /**
+    
+      /**
      * Create a data block containing Roo.data.Records from an XML document.
      * @param {Object} o An Array of row objects which represents the dataset.
-     * @return {Object} data A data block which is used by an Roo.data.Store object as
+     * @return {Object} A data block which is used by an {@link Roo.data.Store} object as
      * a cache of Roo.data.Records.
      */
-    readRecords : function(o){
+    readRecords : function(o)
+    {
         var sid = this.meta ? this.meta.id : null;
        var recordType = this.recordType, fields = recordType.prototype.fields;
        var records = [];
        var root = o;
-           for(var i = 0; i < root.length; i++){
-                   var n = root[i];
-               var values = {};
-               var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
-               for(var j = 0, jlen = fields.length; j < jlen; j++){
-                var f = fields.items[j];
-                var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
-                var v = n[k] !== undefined ? n[k] : f.defaultValue;
-                v = f.convert(v);
-                values[f.name] = v;
-            }
-               var record = new recordType(values, id);
-               record.json = n;
-               records[records.length] = record;
+       for(var i = 0; i < root.length; i++){
+               var n = root[i];
+           var values = {};
+           var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
+           for(var j = 0, jlen = fields.length; j < jlen; j++){
+               var f = fields.items[j];
+               var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
+               var v = n[k] !== undefined ? n[k] : f.defaultValue;
+               v = f.convert(v);
+               values[f.name] = v;
            }
-           return {
-               records : records,
-               totalRecords : records.length
-           };
+           var record = new recordType(values, id);
+           record.json = n;
+           records[records.length] = record;
+       }
+       return {
+           records : records,
+           totalRecords : records.length
+       };
+    },
+    // used when loading children.. @see loadDataFromChildren
+    toLoadData: function(rec)
+    {
+       // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
+       return typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
+       
     }
+    
+    
 });/*
  * - LGPL
  * * 
@@ -13264,12 +13432,12 @@ Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.TriggerField, {
     multiple : false,
     
     /**
-     * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid")
+     * @cfg {String} invalidClass DEPRICATED - uses BS4 is-valid now
      */
     invalidClass : "has-warning",
     
     /**
-     * @cfg {String} validClass The CSS class to use when marking a field valid (defaults to "x-form-invalid")
+     * @cfg {String} validClass DEPRICATED - uses BS4 is-valid now
      */
     validClass : "has-success",
     
@@ -15252,8 +15420,7 @@ Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.TriggerField, {
         if(!this.multiple && this.showToggleBtn){
             
             var caret = {
-                        tag: 'span',
-                        cls: 'caret'
+                cls: 'caret'
             };
             
             if (this.caret != false) {
@@ -15268,7 +15435,7 @@ Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.TriggerField, {
                 tag :'span',
                 cls : 'input-group-addon input-group-append input-group-text btn dropdown-toggle',
                 cn : [
-                    caret,
+                    Roo.bootstrap.version == 3 ? caret : '',
                     {
                         tag: 'span',
                         cls: 'combobox-clear',
@@ -18295,7 +18462,7 @@ Roo.extend(Roo.bootstrap.TabGroup, Roo.bootstrap.Column,  {
     {
         if(this.transition || typeof(pan) == 'undefined'){
             Roo.log("waiting for the transitionend");
-            return;
+            return false;
         }
         
         if (typeof(pan) == 'number') {
@@ -18327,22 +18494,28 @@ Roo.extend(Roo.bootstrap.TabGroup, Roo.bootstrap.Column,  {
         
         if (this.carousel && typeof(Roo.get(document.body).dom.style.transition) != 'undefined') {
             
+            //class="carousel-item carousel-item-next carousel-item-left"
+            
             this.transition = true;
             var dir = this.indexOfPanel(pan) > this.indexOfPanel(cur)  ? 'next' : 'prev';
             var lr = dir == 'next' ? 'left' : 'right';
             pan.el.addClass(dir); // or prev
+            pan.el.addClass('carousel-item-' + dir); // or prev
             pan.el.dom.offsetWidth; // find the offset with - causing a reflow?
             cur.el.addClass(lr); // or right
             pan.el.addClass(lr);
+            cur.el.addClass('carousel-item-' +lr); // or right
+            pan.el.addClass('carousel-item-' +lr);
+            
             
             var _this = this;
             cur.el.on('transitionend', function() {
                 Roo.log("trans end?");
                 
-                pan.el.removeClass([lr,dir]);
+                pan.el.removeClass([lr,dir, 'carousel-item-' + lr, 'carousel-item-' + dir]);
                 pan.setActive(true);
                 
-                cur.el.removeClass([lr]);
+                cur.el.removeClass([lr, 'carousel-item-' + lr]);
                 cur.setActive(false);
                 
                 _this.transition = false;
@@ -18534,10 +18707,12 @@ Roo.extend(Roo.bootstrap.TabPanel, Roo.bootstrap.Component,  {
     href : '',
     
     getAutoCreate : function(){
-        var cfg = {
+        
+       
+       var cfg = {
             tag: 'div',
             // item is needed for carousel - not sure if it has any effect otherwise
-            cls: 'tab-pane item' + ((this.href.length) ? ' clickable ' : ''),
+            cls: 'carousel-item tab-pane item' + ((this.href.length) ? ' clickable ' : ''),
             html: this.html || ''
         };
         
@@ -18549,6 +18724,7 @@ Roo.extend(Roo.bootstrap.TabPanel, Roo.bootstrap.Component,  {
             cfg.tabId = this.tabId;
         }
         
+       
         
         return cfg;
     },
@@ -20759,6 +20935,11 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
     inline: false,
     tooltip : '',
     
+    // checkbox success does not make any sense really.. 
+    invalidClass : "",
+    validClass : "",
+    
+    
     getAutoCreate : function()
     {
         var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
@@ -20859,6 +21040,22 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
             }
             
         }
+        var boxLabelCfg = false;
+        
+        if(this.boxLabel){
+           
+            boxLabelCfg = {
+                tag: 'label',
+                //'for': id, // box label is handled by onclick - so no for...
+                cls: 'box-label',
+                html: this.boxLabel
+            };
+            if(this.tooltip){
+                boxLabelCfg.tooltip = this.tooltip;
+            }
+             
+        }
+        
         
         if (align ==='left' && this.fieldLabel.length) {
 //                Roo.log("left and has label");
@@ -20877,6 +21074,10 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
                 }
             ];
             
+            if (boxLabelCfg) {
+                cfg.cn[1].cn.push(boxLabelCfg);
+            }
+            
             if(this.labelWidth > 12){
                 cfg.cn[0].style = "width: " + this.labelWidth + 'px';
             }
@@ -20920,29 +21121,22 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
                     inputblock
                     
                 ];
+                if (boxLabelCfg) {
+                    cfg.cn.push(boxLabelCfg);
+                }
 
         } else {
             
 //                Roo.log(" no label && no align");
                 cfg.cn = [  inputblock ] ;
-                
+                if (boxLabelCfg) {
+                    cfg.cn.push(boxLabelCfg);
+                }
+
                 
         }
         
-        if(this.boxLabel){
-             var boxLabelCfg = {
-                tag: 'label',
-                //'for': id, // box label is handled by onclick - so no for...
-                cls: 'box-label',
-                html: this.boxLabel
-            };
-            
-            if(this.tooltip){
-                boxLabelCfg.tooltip = this.tooltip;
-            }
-             
-            cfg.cn.push(boxLabelCfg);
-        }
+       
         
         if(this.inputType != 'radio'){
             cfg.cn.push(hidden);
@@ -21194,16 +21388,28 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
 
         if(this.inputType == 'radio'){
             Roo.each(this.el.up('form').select('input[name='+this.name+']', true).elements, function(e){
-                e.findParent('.form-group', false, true).removeClass([_this.invalidClass, _this.validClass]);
-                e.findParent('.form-group', false, true).addClass(_this.validClass);
+                var fg = e.findParent('.form-group', false, true);
+                if (Roo.bootstrap.version == 3) {
+                    fg.removeClass([_this.invalidClass, _this.validClass]);
+                    fg.addClass(_this.validClass);
+                } else {
+                    fg.removeClass(['is-valid', 'is-invalid']);
+                    fg.addClass('is-valid');
+                }
             });
             
             return;
         }
 
         if(!this.groupId){
-            this.el.findParent('.form-group', false, true).removeClass([this.invalidClass, this.validClass]);
-            this.el.findParent('.form-group', false, true).addClass(this.validClass);
+            var fg = this.el.findParent('.form-group', false, true);
+            if (Roo.bootstrap.version == 3) {
+                fg.removeClass([this.invalidClass, this.validClass]);
+                fg.addClass(this.validClass);
+            } else {
+                fg.removeClass(['is-valid', 'is-invalid']);
+                fg.addClass('is-valid');
+            }
             return;
         }
         
@@ -21214,8 +21420,14 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
         }
         
         for(var i in group){
-            group[i].el.findParent('.form-group', false, true).removeClass([this.invalidClass, this.validClass]);
-            group[i].el.findParent('.form-group', false, true).addClass(this.validClass);
+            var fg = group[i].el.findParent('.form-group', false, true);
+            if (Roo.bootstrap.version == 3) {
+                fg.removeClass([this.invalidClass, this.validClass]);
+                fg.addClass(this.validClass);
+            } else {
+                fg.removeClass(['is-valid', 'is-invalid']);
+                fg.addClass('is-valid');
+            }
         }
     },
     
@@ -21244,17 +21456,30 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
         }
             
         if(this.inputType == 'radio'){
+            
             Roo.each(this.el.up('form').select('input[name='+this.name+']', true).elements, function(e){
-                e.findParent('.form-group', false, true).removeClass([_this.invalidClass, _this.validClass]);
-                e.findParent('.form-group', false, true).addClass(_this.invalidClass);
+                var fg = e.findParent('.form-group', false, true);
+                if (Roo.bootstrap.version == 3) {
+                    fg.removeClass([_this.invalidClass, _this.validClass]);
+                    fg.addClass(_this.invalidClass);
+                } else {
+                    fg.removeClass(['is-invalid', 'is-valid']);
+                    fg.addClass('is-invalid');
+                }
             });
             
             return;
         }
         
         if(!this.groupId){
-            this.el.findParent('.form-group', false, true).removeClass([this.invalidClass, this.validClass]);
-            this.el.findParent('.form-group', false, true).addClass(this.invalidClass);
+            var fg = this.el.findParent('.form-group', false, true);
+            if (Roo.bootstrap.version == 3) {
+                fg.removeClass([_this.invalidClass, _this.validClass]);
+                fg.addClass(_this.invalidClass);
+            } else {
+                fg.removeClass(['is-invalid', 'is-valid']);
+                fg.addClass('is-invalid');
+            }
             return;
         }
         
@@ -21265,8 +21490,14 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
         }
         
         for(var i in group){
-            group[i].el.findParent('.form-group', false, true).removeClass([this.invalidClass, this.validClass]);
-            group[i].el.findParent('.form-group', false, true).addClass(this.invalidClass);
+            var fg = group[i].el.findParent('.form-group', false, true);
+            if (Roo.bootstrap.version == 3) {
+                fg.removeClass([_this.invalidClass, _this.validClass]);
+                fg.addClass(_this.invalidClass);
+            } else {
+                fg.removeClass(['is-invalid', 'is-valid']);
+                fg.addClass('is-invalid');
+            }
         }
         
     },
@@ -21280,8 +21511,8 @@ Roo.extend(Roo.bootstrap.CheckBox, Roo.bootstrap.Input,  {
         var label = Roo.bootstrap.FieldLabel.get(this.name + '-group');
         
         if (label && label.iconEl) {
-            label.iconEl.removeClass(label.validClass);
-            label.iconEl.removeClass(label.invalidClass);
+            label.iconEl.removeClass([ label.validClass, label.invalidClass ]);
+            label.iconEl.removeClass(['is-invalid', 'is-valid']);
         }
     },
     
@@ -22144,17 +22375,32 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
             html = this.cleanHtml(html);
             // fix up the special chars.. normaly like back quotes in word...
             // however we do not want to do this with chinese..
-            html = html.replace(/([\x80-\uffff])/g, function (a, b) {
-                var cc = b.charCodeAt();
-                if (
+            html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
+                
+                var cc = match.charCodeAt();
+
+                // Get the character value, handling surrogate pairs
+                if (match.length == 2) {
+                    // It's a surrogate pair, calculate the Unicode code point
+                    var high = match.charCodeAt(0) - 0xD800;
+                    var low  = match.charCodeAt(1) - 0xDC00;
+                    cc = (high * 0x400) + low + 0x10000;
+                }  else if (
                     (cc >= 0x4E00 && cc < 0xA000 ) ||
                     (cc >= 0x3400 && cc < 0x4E00 ) ||
                     (cc >= 0xf900 && cc < 0xfb00 )
                 ) {
-                        return b;
-                }
-                return "&#"+cc+";" 
+                        return match;
+                }  
+         
+                // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
+                return "&#" + cc + ";";
+                
+                
             });
+            
+            
+             
             if(this.owner.fireEvent('beforesync', this, html) !== false){
                 this.el.dom.value = html;
                 this.owner.fireEvent('sync', this, html);
@@ -22338,7 +22584,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
     insertTag : function(tg)
     {
         // could be a bit smarter... -> wrap the current selected tRoo..
-        if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
+        if (tg.toLowerCase() == 'span' ||
+            tg.toLowerCase() == 'code' ||
+            tg.toLowerCase() == 'sup' ||
+            tg.toLowerCase() == 'sub' 
+            ) {
             
             range = this.createRange(this.getSelection());
             var wrappingNode = this.doc.createElement(tg.toLowerCase());
@@ -22834,6 +23084,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         
         var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
         
+        // spans with no attributes - just remove them..
+        if ((!node.attributes || !node.attributes.length) && lcname == 'span') { 
+            remove_keep_children = true;
+        }
+        
         // remove <a name=....> as rendering on yahoo mailer is borked with this.
         // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
         
@@ -22854,6 +23109,10 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
         }
         
         if (!node.attributes || !node.attributes.length) {
+            
+          
+            
+            
             this.cleanUpChildren(node);
             return;
         }
@@ -22950,11 +23209,11 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
             
             if (a.name == 'class') {
                 if (a.value.match(/^Mso/)) {
-                    node.className = '';
+                    node.removeAttribute('class');
                 }
                 
                 if (a.value.match(/^body$/)) {
-                    node.className = '';
+                    node.removeAttribute('class');
                 }
                 continue;
             }
@@ -22975,12 +23234,29 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
      */
     cleanWord : function(node)
     {
-        
-        
         if (!node) {
             this.cleanWord(this.doc.body);
             return;
         }
+        
+        if(
+                node.nodeName == 'SPAN' &&
+                !node.hasAttributes() &&
+                node.childNodes.length == 1 &&
+                node.firstChild.nodeName == "#text"  
+        ) {
+            var textNode = node.firstChild;
+            node.removeChild(textNode);
+            if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
+                node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
+            }
+            node.parentNode.insertBefore(textNode, node);
+            if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
+                node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
+            }
+            node.parentNode.removeChild(node);
+        }
+        
         if (node.nodeName == "#text") {
             // clean up silly Windows -- stuff?
             return; 
@@ -22995,16 +23271,20 @@ Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
             node.parentNode.removeChild(node);
             return;
         }
-        
+        //Roo.log(node.tagName);
         // remove - but keep children..
-        if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
+        if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
+            //Roo.log('-- removed');
             while (node.childNodes.length) {
                 var cn = node.childNodes[0];
                 node.removeChild(cn);
                 node.parentNode.insertBefore(cn, node);
+                // move node to parent - and clean it..
+                this.cleanWord(cn);
             }
             node.parentNode.removeChild(node);
-            this.iterateChildren(node, this.cleanWord);
+            /// no need to iterate chidlren = it's got none..
+            //this.iterateChildren(node, this.cleanWord);
             return;
         }
         // clean styles
@@ -23910,9 +24190,7 @@ Roo.extend(Roo.bootstrap.HtmlEditor, Roo.bootstrap.TextArea,  {
     /**
      * @cfg {String} inputType @hide
      */
-    /**
-     * @cfg {String} invalidClass @hide
-     */
+     
     /**
      * @cfg {String} invalidText @hide
      */
@@ -23934,6 +24212,7 @@ Roo.namespace('Roo.bootstrap.htmleditor');
  * @class Roo.bootstrap.HtmlEditorToolbar1
  * Basic Toolbar
  * 
+ * @example
  * Usage:
  *
  new Roo.bootstrap.HtmlEditor({
@@ -27060,17 +27339,15 @@ Roo.apply(Roo.bootstrap.LocationPicker, {
     }
     
     
-});/*
- * - LGPL
- *
- * Alert
- * 
- */
-
-/**
+});/**
  * @class Roo.bootstrap.Alert
  * @extends Roo.bootstrap.Component
- * Bootstrap Alert class
+ * Bootstrap Alert class - shows an alert area box
+ * eg
+ * <div class="alert alert-danger" role="alert"><span class="fa fa-exclamation-triangle"></span><span class="sr-only">Error:</span>
+  Enter a valid email address
+</div>
+ * @licence LGPL
  * @cfg {String} title The title of alert
  * @cfg {String} html The content of alert
  * @cfg {String} weight (  success | info | warning | danger )
@@ -30334,8 +30611,8 @@ Roo.extend(Roo.bootstrap.NavProgressItem, Roo.bootstrap.Component,  {
  * @cfg {String} cls class of the element
  * @cfg {String} target label target 
  * @cfg {Boolean} allowBlank (true|false) target allowBlank default true
- * @cfg {String} invalidClass default "text-warning"
- * @cfg {String} validClass default "text-success"
+ * @cfg {String} invalidClass DEPRICATED - BS4 uses is-invalid
+ * @cfg {String} validClass DEPRICATED - BS4 uses is-valid
  * @cfg {String} iconTooltip default "This field is required"
  * @cfg {String} indicatorpos (left|right) default left
  * 
@@ -30457,10 +30734,14 @@ Roo.extend(Roo.bootstrap.FieldLabel, Roo.bootstrap.Component,  {
             this.indicator.removeClass('visible');
             this.indicator.addClass('invisible');
         }
+        if (Roo.bootstrap.version == 3) {
+           this.el.removeClass(this.invalidClass);
+           this.el.addClass(this.validClass);
+       } else {
+           this.el.removeClass('is-invalid');
+            this.el.addClass('is-valid');
+       }
         
-        this.el.removeClass(this.invalidClass);
-        
-        this.el.addClass(this.validClass);
         
         this.fireEvent('valid', this);
     },
@@ -30475,10 +30756,14 @@ Roo.extend(Roo.bootstrap.FieldLabel, Roo.bootstrap.Component,  {
             this.indicator.removeClass('invisible');
             this.indicator.addClass('visible');
         }
+          if (Roo.bootstrap.version == 3) {
+           this.el.removeClass(this.validClass);
+           this.el.addClass(this.invalidClass);
+       } else {
+           this.el.removeClass('is-valid');
+            this.el.addClass('is-invalid');
+       }
         
-        this.el.removeClass(this.validClass);
-        
-        this.el.addClass(this.invalidClass);
         
         this.fireEvent('invalid', this, msg);
     }
@@ -34050,21 +34335,23 @@ Roo.extend(Roo.bootstrap.RadioSet, Roo.bootstrap.Input,  {
                 }
             ]
         };
-        
-        if(this.indicatorpos == 'left'){
-            label.cn.unshift({
-                tag : 'i',
-                cls : 'roo-required-indicator left-indicator text-danger fa fa-lg fa-star',
-                tooltip : 'This field is required'
-            });
-        } else {
-            label.cn.push({
-                tag : 'i',
-                cls : 'roo-required-indicator right-indicator text-danger fa fa-lg fa-star',
-                tooltip : 'This field is required'
-            });
+        if (Roo.bootstrap.version == 3) {
+            
+            
+            if(this.indicatorpos == 'left'){
+                label.cn.unshift({
+                    tag : 'i',
+                    cls : 'roo-required-indicator left-indicator text-danger fa fa-lg fa-star',
+                    tooltip : 'This field is required'
+                });
+            } else {
+                label.cn.push({
+                    tag : 'i',
+                    cls : 'roo-required-indicator right-indicator text-danger fa fa-lg fa-star',
+                    tooltip : 'This field is required'
+                });
+            }
         }
-        
         var items = {
             tag : 'div',
             cls : 'roo-radio-set-items'
@@ -34216,14 +34503,19 @@ Roo.extend(Roo.bootstrap.RadioSet, Roo.bootstrap.Input,  {
     
     markValid : function()
     {
-        if(this.labelEl.isVisible(true)){
+        if(this.labelEl.isVisible(true) && this.indicatorEl()){
             this.indicatorEl().removeClass('visible');
             this.indicatorEl().addClass('invisible');
         }
         
-        this.el.removeClass([this.invalidClass, this.validClass]);
-        this.el.addClass(this.validClass);
         
+        if (Roo.bootstrap.version == 3) {
+            this.el.removeClass([this.invalidClass, this.validClass]);
+            this.el.addClass(this.validClass);
+        } else {
+            this.el.removeClass(['is-invalid','is-valid']);
+            this.el.addClass(['is-valid']);
+        }
         this.fireEvent('valid', this);
     },
     
@@ -34233,13 +34525,17 @@ Roo.extend(Roo.bootstrap.RadioSet, Roo.bootstrap.Input,  {
             return;
         }
         
-        if(this.labelEl.isVisible(true)){
+        if(this.labelEl.isVisible(true) && this.indicatorEl()){
             this.indicatorEl().removeClass('invisible');
             this.indicatorEl().addClass('visible');
         }
-        
-        this.el.removeClass([this.invalidClass, this.validClass]);
-        this.el.addClass(this.invalidClass);
+        if (Roo.bootstrap.version == 3) {
+            this.el.removeClass([this.invalidClass, this.validClass]);
+            this.el.addClass(this.invalidClass);
+        } else {
+            this.el.removeClass(['is-invalid','is-valid']);
+            this.el.addClass(['is-invalid']);
+        }
         
         this.fireEvent('invalid', this, msg);
         
@@ -34995,6 +35291,9 @@ Roo.bootstrap.layout.Border = function(config){
 Roo.bootstrap.layout.Border.regions =  ["north","south","east","west","center"];
 
 Roo.extend(Roo.bootstrap.layout.Border, Roo.bootstrap.layout.Manager, {
+    
+    parent : false, // this might point to a 'nest' or a ???
+    
     /**
      * Creates and adds a new region if it doesn't already exist.
      * @param {String} target The target region key (north, south, east, west or center).
@@ -35249,13 +35548,18 @@ layout.addxtype({
             delete cfg.items;
         }
         var nb = false;
+       
+       if ( region == 'center') {
+           Roo.log("Center: " + cfg.title);
+       }
+       
         
         switch(cfg.xtype) 
         {
             case 'Content':  // ContentPanel (el, cfg)
             case 'Scroll':  // ContentPanel (el, cfg)
             case 'View': 
-                cfg.autoCreate = true;
+                cfg.autoCreate = cfg.autoCreate || true;
                 ret = new cfg.xns[cfg.xtype](cfg); // new panel!!!!!
                 //} else {
                 //    var el = this.el.createChild();
@@ -35826,6 +36130,12 @@ Roo.extend(Roo.bootstrap.layout.Region, Roo.bootstrap.layout.Basic, {
 
     position: '', // set by wrapper (eg. north/south etc..)
     unrendered_panels : null,  // unrendered panels.
+    
+    tabPosition : false,
+    
+    mgr: false, // points to 'Border'
+    
+    
     createBody : function(){
         /** This region's body element 
         * @type Roo.Element */
@@ -35847,15 +36157,15 @@ Roo.extend(Roo.bootstrap.layout.Region, Roo.bootstrap.layout.Basic, {
         /** This region's title element 
         * @type Roo.Element */
     
-        this.titleEl = dh.append(this.el.dom,
-            {
-                    tag: "div",
-                    unselectable: "on",
-                    cls: "roo-unselectable roo-layout-panel-hd breadcrumb roo-layout-title-" + this.position,
-                    children:[
-                        {tag: "span", cls: "roo-unselectable roo-layout-panel-hd-text", unselectable: "on", html: "&#160;"},
-                        {tag: "div", cls: "roo-unselectable roo-layout-panel-hd-tools", unselectable: "on"}
-                    ]}, true);
+        this.titleEl = dh.append(this.el.dom,  {
+                tag: "div",
+                unselectable: "on",
+                cls: "roo-unselectable roo-layout-panel-hd breadcrumb roo-layout-title-" + this.position,
+                children:[
+                    {tag: "span", cls: "roo-unselectable roo-layout-panel-hd-text", unselectable: "on", html: "&#160;"},
+                    {tag: "div", cls: "roo-unselectable roo-layout-panel-hd-tools", unselectable: "on"}
+                ]
+            }, true);
         
         this.titleEl.enableDisplayMode();
         /** This region's title text element 
@@ -35960,7 +36270,7 @@ Roo.extend(Roo.bootstrap.layout.Region, Roo.bootstrap.layout.Basic, {
         this.margins = c.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
         
         
-        this.bottomTabs = c.tabPosition != "top";
+        this.tabPosition = [ 'top','bottom', 'west'].indexOf(c.tabPosition) > -1 ? c.tabPosition : "top";
         
         this.autoScroll = c.autoScroll || false;
         
@@ -36199,11 +36509,12 @@ Roo.extend(Roo.bootstrap.layout.Region, Roo.bootstrap.layout.Basic, {
         //this.bodyEl.setStyle("overflow", "hidden"); -- this is set in render?
         
         var ts = new Roo.bootstrap.panel.Tabs({
-                el: this.bodyEl.dom,
-                tabPosition: this.bottomTabs ? 'bottom' : 'top',
-                disableTooltips: this.config.disableTabTips,
-                toolbar : this.config.toolbar
-            });
+            el: this.bodyEl.dom,
+            region : this,
+            tabPosition: this.tabPosition ? this.tabPosition  : 'top',
+            disableTooltips: this.config.disableTabTips,
+            toolbar : this.config.toolbar
+        });
         
         if(this.config.hideTabs){
             ts.stripWrap.setDisplayed(false);
@@ -36894,6 +37205,7 @@ Roo.extend(Roo.bootstrap.layout.Center, Roo.bootstrap.layout.Region, {
 
 
 
+
 Roo.bootstrap.layout.North = function(config)
 {
     config.region = 'north';
@@ -37086,8 +37398,7 @@ Roo.extend(Roo.bootstrap.layout.West, Roo.bootstrap.layout.Split, {
         }
         Roo.bootstrap.layout.Region.prototype.updateBox.call(this, box);
     }
-});
-Roo.namespace("Roo.bootstrap.panel");/*
+});Roo.namespace("Roo.bootstrap.panel");/*
  * Based on:
  * Ext JS Library 1.1.1
  * Copyright(c) 2006-2007, Ext JS, LLC.
@@ -37788,6 +38099,7 @@ Roo.bootstrap.panel.Nest = function(config)
     config.layout.monitorWindowResize = false; // turn off autosizing
     this.layout = config.layout;
     this.layout.getEl().addClass("roo-layout-nested-layout");
+    this.layout.parent = this;
     
     
     
@@ -37886,7 +38198,7 @@ panel.addxtype({
         return this.layout.addxtype(cfg);
     
     }
-});        /*
+});/*
  * Based on:
  * Ext JS Library 1.1.1
  * Copyright(c) 2006-2007, Ext JS, LLC.
@@ -37950,16 +38262,48 @@ Roo.bootstrap.panel.Tabs = function(config){
     }
     
     if(this.tabPosition == "bottom"){
+        // if tabs are at the bottom = create the body first.
         this.bodyEl = Roo.get(this.createBody(this.el.dom));
         this.el.addClass("roo-tabs-bottom");
     }
-    this.stripWrap = Roo.get(this.createStrip(this.el.dom), true);
-    this.stripEl = Roo.get(this.createStripList(this.stripWrap.dom), true);
-    this.stripEl.setVisibilityMode(Roo.Element.DISPLAY);
-    this.stripBody = Roo.get(this.stripWrap.dom.firstChild.firstChild, true);
+    // next create the tabs holders
+    
+    if (this.tabPosition == "west"){
+        
+        var reg = this.region; // fake it..
+        while (reg) {
+            if (!reg.mgr.parent) {
+                break;
+            }
+            reg = reg.mgr.parent.region;
+        }
+        Roo.log("got nest?");
+        Roo.log(reg);
+        if (reg.mgr.getRegion('west')) {
+            var ctrdom = reg.mgr.getRegion('west').bodyEl.dom;
+            this.stripWrap = Roo.get(this.createStrip(ctrdom ), true);
+            this.stripEl = Roo.get(this.createStripList(this.stripWrap.dom), true);
+            this.stripEl.setVisibilityMode(Roo.Element.DISPLAY);
+            this.stripBody = Roo.get(this.stripWrap.dom.firstChild.firstChild, true);
+        
+            
+        }
+        
+        
+    } else {
+     
+        this.stripWrap = Roo.get(this.createStrip(this.el.dom), true);
+        this.stripEl = Roo.get(this.createStripList(this.stripWrap.dom), true);
+        this.stripEl.setVisibilityMode(Roo.Element.DISPLAY);
+        this.stripBody = Roo.get(this.stripWrap.dom.firstChild.firstChild, true);
+    }
+    
+    
     if(Roo.isIE){
         Roo.fly(this.stripWrap.dom.firstChild).setStyle("overflow-x", "hidden");
     }
+    
+    // finally - if tabs are at the top, then create the body last..
     if(this.tabPosition != "bottom"){
         /** The body element that contains {@link Roo.TabPanelItem} bodies. +
          * @type Roo.Element
@@ -38050,7 +38394,11 @@ Roo.extend(Roo.bootstrap.panel.Tabs, Roo.util.Observable, {
     /*
      *@cfg {Object} toolbar xtype description of toolbar to show at the right of the tab bar. 
      */
-    toolbar : false,
+    toolbar : false,  // set by caller..
+    
+    region : false, /// set by caller
+    
+    disableTooltips : true, // not used yet...
 
     /**
      * Creates a new {@link Roo.TabPanelItem} by looking for an existing element with the provided id -- if it's not found it creates one.
@@ -38203,6 +38551,8 @@ Roo.extend(Roo.bootstrap.panel.Tabs, Roo.util.Observable, {
      */
     activate : function(id)
     {
+        //Roo.log('activite:'  + id);
+        
         var tab = this.items[id];
         if(!tab){
             return null;
@@ -41319,4 +41669,601 @@ Roo.extend(Roo.bootstrap.MoneyField, Roo.bootstrap.ComboBox, {
         return this.el.select('input.hidden-number-input',true).first();
     }
     
-});
\ No newline at end of file
+});/**
+ * @class Roo.bootstrap.BezierSignature
+ * @extends Roo.bootstrap.Component
+ * Bootstrap BezierSignature class
+ * This script refer to:
+ *    Title: Signature Pad
+ *    Author: szimek
+ *    Availability: https://github.com/szimek/signature_pad
+ *
+ * @constructor
+ * Create a new BezierSignature
+ * @param {Object} config The config object
+ */
+
+Roo.bootstrap.BezierSignature = function(config){
+    Roo.bootstrap.BezierSignature.superclass.constructor.call(this, config);
+    this.addEvents({
+        "resize" : true
+    });
+};
+
+Roo.extend(Roo.bootstrap.BezierSignature, Roo.bootstrap.Component,
+{
+     
+    curve_data: [],
+    
+    is_empty: true,
+    
+    mouse_btn_down: true,
+    
+    /**
+     * @cfg {int} canvas height
+     */
+    canvas_height: '200px',
+    
+    /**
+     * @cfg {float|function} Radius of a single dot.
+     */ 
+    dot_size: false,
+    
+    /**
+     * @cfg {float} Minimum width of a line. Defaults to 0.5.
+     */
+    min_width: 0.5,
+    
+    /**
+     * @cfg {float} Maximum width of a line. Defaults to 2.5.
+     */
+    max_width: 2.5,
+    
+    /**
+     * @cfg {integer} Draw the next point at most once per every x milliseconds. Set it to 0 to turn off throttling. Defaults to 16.
+     */
+    throttle: 16,
+    
+    /**
+     * @cfg {integer} Add the next point only if the previous one is farther than x pixels. Defaults to 5.
+     */
+    min_distance: 5,
+    
+    /**
+     * @cfg {string} Color used to clear the background. Can be any color format accepted by context.fillStyle. Defaults to "rgba(0,0,0,0)" (transparent black). Use a non-transparent color e.g. "rgb(255,255,255)" (opaque white) if you'd like to save signatures as JPEG images.
+     */
+    bg_color: 'rgba(0, 0, 0, 0)',
+    
+    /**
+     * @cfg {string} Color used to draw the lines. Can be any color format accepted by context.fillStyle. Defaults to "black".
+     */
+    dot_color: 'black',
+    
+    /**
+     * @cfg {float} Weight used to modify new velocity based on the previous velocity. Defaults to 0.7.
+     */ 
+    velocity_filter_weight: 0.7,
+    
+    /**
+     * @cfg {function} Callback when stroke begin. 
+     */
+    onBegin: false,
+    
+    /**
+     * @cfg {function} Callback when stroke end.
+     */
+    onEnd: false,
+    
+    getAutoCreate : function()
+    {
+        var cls = 'roo-signature column';
+        
+        if(this.cls){
+            cls += ' ' + this.cls;
+        }
+        
+        var col_sizes = [
+            'lg',
+            'md',
+            'sm',
+            'xs'
+        ];
+        
+        for(var i = 0; i < col_sizes.length; i++) {
+            if(this[col_sizes[i]]) {
+                cls += " col-"+col_sizes[i]+"-"+this[col_sizes[i]];
+            }
+        }
+        
+        var cfg = {
+            tag: 'div',
+            cls: cls,
+            cn: [
+                {
+                    tag: 'div',
+                    cls: 'roo-signature-body',
+                    cn: [
+                        {
+                            tag: 'canvas',
+                            cls: 'roo-signature-body-canvas',
+                            height: this.canvas_height,
+                            width: this.canvas_width
+                        }
+                    ]
+                },
+                {
+                    tag: 'input',
+                    type: 'file',
+                    style: 'display: none'
+                }
+            ]
+        };
+        
+        return cfg;
+    },
+    
+    initEvents: function() 
+    {
+        Roo.bootstrap.BezierSignature.superclass.initEvents.call(this);
+        
+        var canvas = this.canvasEl();
+        
+        // mouse && touch event swapping...
+        canvas.dom.style.touchAction = 'none';
+        canvas.dom.style.msTouchAction = 'none';
+        
+        this.mouse_btn_down = false;
+        canvas.on('mousedown', this._handleMouseDown, this);
+        canvas.on('mousemove', this._handleMouseMove, this);
+        Roo.select('html').first().on('mouseup', this._handleMouseUp, this);
+        
+        if (window.PointerEvent) {
+            canvas.on('pointerdown', this._handleMouseDown, this);
+            canvas.on('pointermove', this._handleMouseMove, this);
+            Roo.select('html').first().on('pointerup', this._handleMouseUp, this);
+        }
+        
+        if ('ontouchstart' in window) {
+            canvas.on('touchstart', this._handleTouchStart, this);
+            canvas.on('touchmove', this._handleTouchMove, this);
+            canvas.on('touchend', this._handleTouchEnd, this);
+        }
+        
+        Roo.EventManager.onWindowResize(this.resize, this, true);
+        
+        // file input event
+        this.fileEl().on('change', this.uploadImage, this);
+        
+        this.clear();
+        
+        this.resize();
+    },
+    
+    resize: function(){
+        
+        var canvas = this.canvasEl().dom;
+        var ctx = this.canvasElCtx();
+        var img_data = false;
+        
+        if(canvas.width > 0) {
+            var img_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
+        }
+        // setting canvas width will clean img data
+        canvas.width = 0;
+        
+        var style = window.getComputedStyle ? 
+            getComputedStyle(this.el.dom, null) : this.el.dom.currentStyle;
+            
+        var padding_left = parseInt(style.paddingLeft) || 0;
+        var padding_right = parseInt(style.paddingRight) || 0;
+        
+        canvas.width = this.el.dom.clientWidth - padding_left - padding_right;
+        
+        if(img_data) {
+            ctx.putImageData(img_data, 0, 0);
+        }
+    },
+    
+    _handleMouseDown: function(e)
+    {
+        if (e.browserEvent.which === 1) {
+            this.mouse_btn_down = true;
+            this.strokeBegin(e);
+        }
+    },
+    
+    _handleMouseMove: function (e)
+    {
+        if (this.mouse_btn_down) {
+            this.strokeMoveUpdate(e);
+        }
+    },
+    
+    _handleMouseUp: function (e)
+    {
+        if (e.browserEvent.which === 1 && this.mouse_btn_down) {
+            this.mouse_btn_down = false;
+            this.strokeEnd(e);
+        }
+    },
+    
+    _handleTouchStart: function (e) {
+        
+        e.preventDefault();
+        if (e.browserEvent.targetTouches.length === 1) {
+            // var touch = e.browserEvent.changedTouches[0];
+            // this.strokeBegin(touch);
+            
+             this.strokeBegin(e); // assume e catching the correct xy...
+        }
+    },
+    
+    _handleTouchMove: function (e) {
+        e.preventDefault();
+        // var touch = event.targetTouches[0];
+        // _this._strokeMoveUpdate(touch);
+        this.strokeMoveUpdate(e);
+    },
+    
+    _handleTouchEnd: function (e) {
+        var wasCanvasTouched = e.target === this.canvasEl().dom;
+        if (wasCanvasTouched) {
+            e.preventDefault();
+            // var touch = event.changedTouches[0];
+            // _this._strokeEnd(touch);
+            this.strokeEnd(e);
+        }
+    },
+    
+    reset: function () {
+        this._lastPoints = [];
+        this._lastVelocity = 0;
+        this._lastWidth = (this.min_width + this.max_width) / 2;
+        this.canvasElCtx().fillStyle = this.dot_color;
+    },
+    
+    strokeMoveUpdate: function(e)
+    {
+        this.strokeUpdate(e);
+        
+        if (this.throttle) {
+            this.throttleStroke(this.strokeUpdate, this.throttle);
+        }
+        else {
+            this.strokeUpdate(e);
+        }
+    },
+    
+    strokeBegin: function(e)
+    {
+        var newPointGroup = {
+            color: this.dot_color,
+            points: []
+        };
+        
+        if (typeof this.onBegin === 'function') {
+            this.onBegin(e);
+        }
+        
+        this.curve_data.push(newPointGroup);
+        this.reset();
+        this.strokeUpdate(e);
+    },
+    
+    strokeUpdate: function(e)
+    {
+        var rect = this.canvasEl().dom.getBoundingClientRect();
+        var point = new this.Point(e.xy[0] - rect.left, e.xy[1] - rect.top, new Date().getTime());
+        var lastPointGroup = this.curve_data[this.curve_data.length - 1];
+        var lastPoints = lastPointGroup.points;
+        var lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
+        var isLastPointTooClose = lastPoint
+            ? point.distanceTo(lastPoint) <= this.min_distance
+            : false;
+        var color = lastPointGroup.color;
+        if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
+            var curve = this.addPoint(point);
+            if (!lastPoint) {
+                this.drawDot({color: color, point: point});
+            }
+            else if (curve) {
+                this.drawCurve({color: color, curve: curve});
+            }
+            lastPoints.push({
+                time: point.time,
+                x: point.x,
+                y: point.y
+            });
+        }
+    },
+    
+    strokeEnd: function(e)
+    {
+        this.strokeUpdate(e);
+        if (typeof this.onEnd === 'function') {
+            this.onEnd(e);
+        }
+    },
+    
+    addPoint:  function (point) {
+        var _lastPoints = this._lastPoints;
+        _lastPoints.push(point);
+        if (_lastPoints.length > 2) {
+            if (_lastPoints.length === 3) {
+                _lastPoints.unshift(_lastPoints[0]);
+            }
+            var widths = this.calculateCurveWidths(_lastPoints[1], _lastPoints[2]);
+            var curve = this.Bezier.fromPoints(_lastPoints, widths, this);
+            _lastPoints.shift();
+            return curve;
+        }
+        return null;
+    },
+    
+    calculateCurveWidths: function (startPoint, endPoint) {
+        var velocity = this.velocity_filter_weight * endPoint.velocityFrom(startPoint) +
+            (1 - this.velocity_filter_weight) * this._lastVelocity;
+
+        var newWidth = Math.max(this.max_width / (velocity + 1), this.min_width);
+        var widths = {
+            end: newWidth,
+            start: this._lastWidth
+        };
+        
+        this._lastVelocity = velocity;
+        this._lastWidth = newWidth;
+        return widths;
+    },
+    
+    drawDot: function (_a) {
+        var color = _a.color, point = _a.point;
+        var ctx = this.canvasElCtx();
+        var width = typeof this.dot_size === 'function' ? this.dot_size() : this.dot_size;
+        ctx.beginPath();
+        this.drawCurveSegment(point.x, point.y, width);
+        ctx.closePath();
+        ctx.fillStyle = color;
+        ctx.fill();
+    },
+    
+    drawCurve: function (_a) {
+        var color = _a.color, curve = _a.curve;
+        var ctx = this.canvasElCtx();
+        var widthDelta = curve.endWidth - curve.startWidth;
+        var drawSteps = Math.floor(curve.length()) * 2;
+        ctx.beginPath();
+        ctx.fillStyle = color;
+        for (var i = 0; i < drawSteps; i += 1) {
+        var t = i / drawSteps;
+        var tt = t * t;
+        var ttt = tt * t;
+        var u = 1 - t;
+        var uu = u * u;
+        var uuu = uu * u;
+        var x = uuu * curve.startPoint.x;
+        x += 3 * uu * t * curve.control1.x;
+        x += 3 * u * tt * curve.control2.x;
+        x += ttt * curve.endPoint.x;
+        var y = uuu * curve.startPoint.y;
+        y += 3 * uu * t * curve.control1.y;
+        y += 3 * u * tt * curve.control2.y;
+        y += ttt * curve.endPoint.y;
+        var width = curve.startWidth + ttt * widthDelta;
+        this.drawCurveSegment(x, y, width);
+        }
+        ctx.closePath();
+        ctx.fill();
+    },
+    
+    drawCurveSegment: function (x, y, width) {
+        var ctx = this.canvasElCtx();
+        ctx.moveTo(x, y);
+        ctx.arc(x, y, width, 0, 2 * Math.PI, false);
+        this.is_empty = false;
+    },
+    
+    clear: function()
+    {
+        var ctx = this.canvasElCtx();
+        var canvas = this.canvasEl().dom;
+        ctx.fillStyle = this.bg_color;
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+        ctx.fillRect(0, 0, canvas.width, canvas.height);
+        this.curve_data = [];
+        this.reset();
+        this.is_empty = true;
+    },
+    
+    fileEl: function()
+    {
+        return  this.el.select('input',true).first();
+    },
+    
+    canvasEl: function()
+    {
+        return this.el.select('canvas',true).first();
+    },
+    
+    canvasElCtx: function()
+    {
+        return this.el.select('canvas',true).first().dom.getContext('2d');
+    },
+    
+    getImage: function(type)
+    {
+        if(this.is_empty) {
+            return false;
+        }
+        
+        // encryption ?
+        return this.canvasEl().dom.toDataURL('image/'+type, 1);
+    },
+    
+    drawFromImage: function(img_src)
+    {
+        var img = new Image();
+        
+        img.onload = function(){
+            this.canvasElCtx().drawImage(img, 0, 0);
+        }.bind(this);
+        
+        img.src = img_src;
+        
+        this.is_empty = false;
+    },
+    
+    selectImage: function()
+    {
+        this.fileEl().dom.click();
+    },
+    
+    uploadImage: function(e)
+    {
+        var reader = new FileReader();
+        
+        reader.onload = function(e){
+            var img = new Image();
+            img.onload = function(){
+                this.reset();
+                this.canvasElCtx().drawImage(img, 0, 0);
+            }.bind(this);
+            img.src = e.target.result;
+        }.bind(this);
+        
+        reader.readAsDataURL(e.target.files[0]);
+    },
+    
+    // Bezier Point Constructor
+    Point: (function () {
+        function Point(x, y, time) {
+            this.x = x;
+            this.y = y;
+            this.time = time || Date.now();
+        }
+        Point.prototype.distanceTo = function (start) {
+            return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
+        };
+        Point.prototype.equals = function (other) {
+            return this.x === other.x && this.y === other.y && this.time === other.time;
+        };
+        Point.prototype.velocityFrom = function (start) {
+            return this.time !== start.time
+            ? this.distanceTo(start) / (this.time - start.time)
+            : 0;
+        };
+        return Point;
+    }()),
+    
+    
+    // Bezier Constructor
+    Bezier: (function () {
+        function Bezier(startPoint, control2, control1, endPoint, startWidth, endWidth) {
+            this.startPoint = startPoint;
+            this.control2 = control2;
+            this.control1 = control1;
+            this.endPoint = endPoint;
+            this.startWidth = startWidth;
+            this.endWidth = endWidth;
+        }
+        Bezier.fromPoints = function (points, widths, scope) {
+            var c2 = this.calculateControlPoints(points[0], points[1], points[2], scope).c2;
+            var c3 = this.calculateControlPoints(points[1], points[2], points[3], scope).c1;
+            return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
+        };
+        Bezier.calculateControlPoints = function (s1, s2, s3, scope) {
+            var dx1 = s1.x - s2.x;
+            var dy1 = s1.y - s2.y;
+            var dx2 = s2.x - s3.x;
+            var dy2 = s2.y - s3.y;
+            var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
+            var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
+            var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
+            var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
+            var dxm = m1.x - m2.x;
+            var dym = m1.y - m2.y;
+            var k = l2 / (l1 + l2);
+            var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
+            var tx = s2.x - cm.x;
+            var ty = s2.y - cm.y;
+            return {
+                c1: new scope.Point(m1.x + tx, m1.y + ty),
+                c2: new scope.Point(m2.x + tx, m2.y + ty)
+            };
+        };
+        Bezier.prototype.length = function () {
+            var steps = 10;
+            var length = 0;
+            var px;
+            var py;
+            for (var i = 0; i <= steps; i += 1) {
+                var t = i / steps;
+                var cx = this.point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
+                var cy = this.point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
+                if (i > 0) {
+                    var xdiff = cx - px;
+                    var ydiff = cy - py;
+                    length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
+                }
+                px = cx;
+                py = cy;
+            }
+            return length;
+        };
+        Bezier.prototype.point = function (t, start, c1, c2, end) {
+            return (start * (1.0 - t) * (1.0 - t) * (1.0 - t))
+            + (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
+            + (3.0 * c2 * (1.0 - t) * t * t)
+            + (end * t * t * t);
+        };
+        return Bezier;
+    }()),
+    
+    throttleStroke: function(fn, wait) {
+      if (wait === void 0) { wait = 250; }
+      var previous = 0;
+      var timeout = null;
+      var result;
+      var storedContext;
+      var storedArgs;
+      var later = function () {
+          previous = Date.now();
+          timeout = null;
+          result = fn.apply(storedContext, storedArgs);
+          if (!timeout) {
+              storedContext = null;
+              storedArgs = [];
+          }
+      };
+      return function wrapper() {
+          var args = [];
+          for (var _i = 0; _i < arguments.length; _i++) {
+              args[_i] = arguments[_i];
+          }
+          var now = Date.now();
+          var remaining = wait - (now - previous);
+          storedContext = this;
+          storedArgs = args;
+          if (remaining <= 0 || remaining > wait) {
+              if (timeout) {
+                  clearTimeout(timeout);
+                  timeout = null;
+              }
+              previous = now;
+              result = fn.apply(storedContext, storedArgs);
+              if (!timeout) {
+                  storedContext = null;
+                  storedArgs = [];
+              }
+          }
+          else if (!timeout) {
+              timeout = window.setTimeout(later, remaining);
+          }
+          return result;
+      };
+  }
+  
+});
+
+
\ No newline at end of file