Fixes tabdrop memory leak U4-6907 Bootstrap tab drop has memory leaks, fixes tab/header to use the correct bootstrap tab api, simplifies the header code dramatically and speeds up processing. Fixes mem leak with delayedMouseLeave.directive
This commit is contained in:
@@ -25,7 +25,6 @@
|
||||
"jquery-ui": "1.10.3",
|
||||
"angular-dynamic-locale": "~0.1.27",
|
||||
"tinymce": "~4.1.10",
|
||||
"bootstrap-tabdrop": "~1.0.0",
|
||||
"codemirror": "~5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
62
src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/README.md
Normal file
62
src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
bootstrap-tabdrop
|
||||
=================
|
||||
|
||||
*****************************************************************
|
||||
NOTE: THIS IS A CUSTOM FIXED VERSION!!!!!!!!!!!!!!!!!!!!!!
|
||||
- THE ORIGINAL HAS A MEMORY LEAK, SO WE'VE HAD TO EMBED THIS
|
||||
INTO THE CORE WITH THE FIX
|
||||
|
||||
--- UMBRACO CORE TEAM
|
||||
*****************************************************************
|
||||
|
||||
A dropdown tab tool for @twitter bootstrap forked from Stefan Petre's (of eyecon.ro),
|
||||
|
||||
The dropdown tab appears when your tabs do not all fit in the same row.
|
||||
|
||||
Original site and examples: http://www.eyecon.ro/bootstrap-tabdrop/
|
||||
|
||||
Added functionality: Displays the text of an active tab selected from the dropdown list instead of the text option on the dropdown tab.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
* [Bootstrap](http://twitter.github.com/bootstrap/) 2.0.4+
|
||||
* [jQuery](http://jquery.com/) 1.7.1+
|
||||
|
||||
## Example
|
||||
|
||||
No additional HTML needed - the script adds it when the dropdown tab is needed.
|
||||
|
||||
Using bootstrap-tabdrop.js
|
||||
Call the tab drop via javascript on .nav-tabs and .nav-pills:
|
||||
```js
|
||||
$('.nav-pills, .nav-tabs').tabdrop()
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### text
|
||||
Type: string
|
||||
Default: icon
|
||||
```html
|
||||
<i class="icon-align-justify"></i>
|
||||
```
|
||||
To change the default value, call
|
||||
```javascript
|
||||
.tabdrop({text: "your text here"});
|
||||
```
|
||||
when initalizing the tabdrop. The displayed value will change when a tab is selected from the dropdown list.
|
||||
|
||||
### Methods
|
||||
|
||||
```js
|
||||
.tabdrop(options)
|
||||
```
|
||||
|
||||
Initializes an tab drop.
|
||||
|
||||
```js
|
||||
.tabdrop('layout')
|
||||
```
|
||||
|
||||
Checks if the tabs fit in one single row.
|
||||
132
src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/bootstrap-tabdrop.js
vendored
Normal file
132
src/Umbraco.Web.UI.Client/lib/bootstrap-tabdrop/bootstrap-tabdrop.js
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/* =========================================================
|
||||
* bootstrap-tabdrop.js
|
||||
* http://www.eyecon.ro/bootstrap-tabdrop
|
||||
* =========================================================
|
||||
* Copyright 2012 Stefan Petre
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================= */
|
||||
|
||||
/*****************************************************************
|
||||
* NOTE: THIS IS A CUSTOM FIXED VERSION!!!!!!!!!!!!!!!!!!!!!!
|
||||
* - THE ORIGINAL HAS A MEMORY LEAK, SO WE'VE HAD TO EMBED THIS
|
||||
* INTO THE CORE WITH THE FIX
|
||||
*
|
||||
* --- UMBRACO CORE TEAM
|
||||
*****************************************************************/
|
||||
|
||||
!function( $ ) {
|
||||
|
||||
var WinReszier = (function(){
|
||||
var registered = [];
|
||||
var inited = false;
|
||||
var timer;
|
||||
var resize = function(ev) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(notify, 100);
|
||||
};
|
||||
var notify = function() {
|
||||
for(var i=0, cnt=registered.length; i<cnt; i++) {
|
||||
registered[i].apply();
|
||||
}
|
||||
};
|
||||
return {
|
||||
register: function(fn) {
|
||||
registered.push(fn);
|
||||
if (inited === false) {
|
||||
$(window).bind('resize', resize);
|
||||
inited = true;
|
||||
}
|
||||
},
|
||||
unregister: function(fn) {
|
||||
var index = registered.indexOf(fn);
|
||||
if (index > -1) {
|
||||
registered.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
var TabDrop = function(element, options) {
|
||||
this.element = $(element);
|
||||
this.dropdown = $('<li class="dropdown hide pull-right tabdrop">' +
|
||||
'<a class="dropdown-toggle" data-toggle="dropdown" href="#">' +
|
||||
options.text + ' <b class="caret"></b></a>' +
|
||||
'<ul class="dropdown-menu"></ul></li>').prependTo(this.element);
|
||||
if (this.element.parent().is('.tabs-below')) {
|
||||
this.dropdown.addClass('dropup');
|
||||
}
|
||||
this.resizeCallback = $.proxy(this.layout, this);
|
||||
WinReszier.register(this.resizeCallback);
|
||||
this.layout();
|
||||
};
|
||||
|
||||
TabDrop.prototype = {
|
||||
constructor: TabDrop,
|
||||
|
||||
layout: function() {
|
||||
var collection = [];
|
||||
this.dropdown.removeClass('hide');
|
||||
this.element
|
||||
.append(this.dropdown.find('li'))
|
||||
.find('>li')
|
||||
.not('.tabdrop')
|
||||
.each(function(){
|
||||
if(this.offsetTop > 0) {
|
||||
collection.push(this);
|
||||
}
|
||||
});
|
||||
if (collection.length > 0) {
|
||||
collection = $(collection);
|
||||
this.dropdown
|
||||
.find('ul')
|
||||
.empty()
|
||||
.append(collection);
|
||||
if (this.dropdown.find('.active').length == 1) {
|
||||
this.dropdown.addClass('active');
|
||||
} else {
|
||||
this.dropdown.removeClass('active');
|
||||
}
|
||||
} else {
|
||||
this.dropdown.addClass('hide');
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.dropdown.html();
|
||||
WinReszier.unregister(this.resizeCallback);
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.tabdrop = function ( option ) {
|
||||
return this.each(function () {
|
||||
var $this = $(this),
|
||||
data = $this.data('tabdrop'),
|
||||
options = typeof option === 'object' && option;
|
||||
if (!data) {
|
||||
$this.data('tabdrop', (data = new TabDrop(this, $.extend({},
|
||||
$.fn.tabdrop.defaults,options))));
|
||||
}
|
||||
if (typeof option == 'string') {
|
||||
data[option]();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.tabdrop.defaults = {
|
||||
text: '<i class="icon-align-justify"></i>'
|
||||
};
|
||||
|
||||
$.fn.tabdrop.Constructor = TabDrop;
|
||||
|
||||
}(window.jQuery);
|
||||
@@ -19,51 +19,13 @@ angular.module("umbraco.directives")
|
||||
tabs: "="
|
||||
},
|
||||
link: function (scope, iElement, iAttrs) {
|
||||
|
||||
var maxTabs = 4;
|
||||
|
||||
function collectFromDom(activeTab){
|
||||
var $panes = $('div.tab-content');
|
||||
|
||||
angular.forEach($panes.find('.tab-pane'), function (pane, index) {
|
||||
var $this = angular.element(pane);
|
||||
|
||||
var id = $this.attr("rel");
|
||||
var label = $this.attr("label");
|
||||
var tab = {id: id, label: label, active: false};
|
||||
if(!activeTab){
|
||||
tab.active = true;
|
||||
activeTab = tab;
|
||||
}
|
||||
|
||||
if ($this.attr("rel") === String(activeTab.id)) {
|
||||
$this.addClass('active');
|
||||
}
|
||||
else {
|
||||
$this.removeClass('active');
|
||||
}
|
||||
|
||||
if(label){
|
||||
scope.visibleTabs.push(tab);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//TODO: We'll need to destroy this I'm assuming!
|
||||
iElement.find('.nav-pills, .nav-tabs').tabdrop();
|
||||
}
|
||||
|
||||
|
||||
scope.showTabs = iAttrs.tabs ? true : false;
|
||||
scope.visibleTabs = [];
|
||||
scope.overflownTabs = [];
|
||||
|
||||
$timeout(function () {
|
||||
collectFromDom(undefined);
|
||||
}, 500);
|
||||
|
||||
//when the tabs change, we need to hack the planet a bit and force the first tab content to be active,
|
||||
//unfortunately twitter bootstrap tabs is not playing perfectly with angular.
|
||||
scope.$watch("tabs", function (newValue, oldValue) {
|
||||
//since tabs are loaded async, we need to put a watch on them to determine
|
||||
// when they are loaded, then we can close the watch
|
||||
var tabWatch = scope.$watch("tabs", function (newValue, oldValue) {
|
||||
|
||||
angular.forEach(newValue, function(val, index){
|
||||
var tab = {id: val.id, label: val.label};
|
||||
@@ -73,16 +35,25 @@ angular.module("umbraco.directives")
|
||||
//don't process if we cannot or have already done so
|
||||
if (!newValue) {return;}
|
||||
if (!newValue.length || newValue.length === 0){return;}
|
||||
|
||||
var activeTab = _.find(newValue, function (item) {
|
||||
return item.active;
|
||||
});
|
||||
|
||||
|
||||
//we need to do a timeout here so that the current sync operation can complete
|
||||
// and update the UI, then this will fire and the UI elements will be available.
|
||||
$timeout(function () {
|
||||
collectFromDom(activeTab);
|
||||
}, 500);
|
||||
|
||||
//use bootstrap tabs API to show the first one
|
||||
iElement.find(".nav-tabs a:first").tab('show');
|
||||
|
||||
//enable the tab drop
|
||||
iElement.find('.nav-pills, .nav-tabs').tabdrop();
|
||||
|
||||
//ensure to destroy tabdrop (unbinds window resize listeners)
|
||||
scope.$on('$destroy', function () {
|
||||
iElement.find('.nav-pills, .nav-tabs').tabdrop("destroy");
|
||||
});
|
||||
|
||||
//stop watching now
|
||||
tabWatch();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,21 +5,31 @@ angular.module("umbraco.directives")
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
var active = false;
|
||||
var fn = $parse(attrs.delayedMouseleave);
|
||||
element.on("mouseleave", function(event) {
|
||||
var callback = function() {
|
||||
fn(scope, {$event:event});
|
||||
|
||||
function mouseLeave(event) {
|
||||
var callback = function () {
|
||||
fn(scope, { $event: event });
|
||||
};
|
||||
|
||||
active = false;
|
||||
$timeout(function(){
|
||||
if(active === false){
|
||||
$timeout(function () {
|
||||
if (active === false) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
}, 650);
|
||||
});
|
||||
}
|
||||
|
||||
element.on("mouseenter", function(event, args){
|
||||
function mouseEnter(event, args){
|
||||
active = true;
|
||||
}
|
||||
|
||||
element.on("mouseleave", mouseLeave);
|
||||
element.on("mouseenter", mouseEnter);
|
||||
|
||||
//unbind!!
|
||||
scope.$on('$destroy', function () {
|
||||
element.off("mouseleave", mouseLeave);
|
||||
element.off("mouseenter", mouseEnter);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<ul ng-show="showTabs" class="nav nav-tabs umb-nav-tabs span12">
|
||||
|
||||
<li ng-class="{active: $first, 'tab-error': tabHasError}" ng-repeat="tab in visibleTabs" val-tab>
|
||||
<li ng-class="{'tab-error': tabHasError}" ng-repeat="tab in visibleTabs" val-tab>
|
||||
<a href="#tab{{tab.id}}" data-toggle="tab">{{ tab.label }}</a>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
'lib/jquery-file-upload/jquery.fileupload-angular.js',
|
||||
|
||||
'lib/bootstrap/js/bootstrap.2.3.2.min.js',
|
||||
'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js',
|
||||
'lib/bootstrap-tabdrop/bootstrap-tabdrop.js',
|
||||
'lib/umbraco/Extensions.js',
|
||||
|
||||
'lib/umbraco/NamespaceManager.js',
|
||||
|
||||
Reference in New Issue
Block a user