diff --git a/services/web/app/views/project/editor/file-tree.jade b/services/web/app/views/project/editor/file-tree.jade index 0a12cece2f..fc4190be31 100644 --- a/services/web/app/views/project/editor/file-tree.jade +++ b/services/web/app/views/project/editor/file-tree.jade @@ -1,4 +1,4 @@ -aside#file-tree(ng-controller="FileTreeController").full-size +aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected': multiSelectedCount > 0 }").full-size .toolbar.toolbar-small.toolbar-alt(ng-if="permissions.write") a( href, @@ -27,7 +27,8 @@ aside#file-tree(ng-controller="FileTreeController").full-size href, ng-click="startRenamingSelected()", tooltip="#{translate('rename')}", - tooltip-placement="bottom" + tooltip-placement="bottom", + ng-show="multiSelectedCount == 0" ) i.fa.fa-pencil a( @@ -81,27 +82,25 @@ aside#file-tree(ng-controller="FileTreeController").full-size ) .entity .entity-name( - ng-click="select()" + ng-click="select($event)" ) //- Just a spacer to align with folders i.fa.fa-fw.toggle i.fa.fa-fw.fa-file span {{ entity.name }} - - - script(type='text/ng-template', id='entityListItemTemplate') li( - ng-class="{ 'selected': entity.selected }", + ng-class="{ 'selected': entity.selected, 'multi-selected': entity.multiSelected }", ng-controller="FileTreeEntityController" ) .entity(ng-if="entity.type != 'folder'") .entity-name( - ng-click="select()" + ng-click="select($event)" ng-dblclick="permissions.write && startRenaming()" draggable="permissions.write" + draggable-helper="draggableHelper" context-menu data-target="context-menu-{{ entity.id }}" context-menu-container="body" @@ -113,6 +112,7 @@ script(type='text/ng-template', id='entityListItemTemplate') i.fa.fa-fw.fa-file(ng-if="entity.type == 'doc'") i.fa.fa-fw.fa-image(ng-if="entity.type == 'file'") + {{ $parent.multiSelectedCount }} span( ng-hide="entity.renaming" ) {{ entity.name }} @@ -126,12 +126,11 @@ script(type='text/ng-template', id='entityListItemTemplate') on-enter="finishRenaming()" ) - span.dropdown( + span.dropdown.entity-menu-toggle( dropdown, - ng-show="entity.selected", ng-if="permissions.write" ) - a.dropdown-toggle(href, dropdown-toggle) + a.dropdown-toggle(href, dropdown-toggle, stop-propagation="click") i.fa.fa-chevron-down ul.dropdown-menu.dropdown-menu-right @@ -140,12 +139,14 @@ script(type='text/ng-template', id='entityListItemTemplate') href ng-click="startRenaming()" right-click="startRenaming()" + ng-show="!entity.multiSelected" ) #{translate("rename")} li a( href ng-click="openDeleteModal()" right-click="openDeleteModal()" + stop-propagation="click" ) #{translate("delete")} div.dropdown.context-menu( @@ -158,20 +159,23 @@ script(type='text/ng-template', id='entityListItemTemplate') href ng-click="startRenaming()" right-click="startRenaming()" + ng-show="!entity.multiSelected" ) #{translate("rename")} li a( href ng-click="openDeleteModal()" right-click="openDeleteModal()" + stop-propagation="click" ) #{translate("delete")} .entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController") .entity-name( - ng-click="select()" + ng-click="select($event)" ng-dblclick="permissions.write && startRenaming()" draggable="permissions.write" + draggable-helper="draggableHelper" droppable="permissions.write" accept=".entity-name" on-drop-callback="onDrop" @@ -194,7 +198,7 @@ script(type='text/ng-template', id='entityListItemTemplate') 'fa-folder': !expanded, \ 'fa-folder-open': expanded \ }" - ng-click="select()" + ng-click="select($event)" ) span( @@ -210,12 +214,11 @@ script(type='text/ng-template', id='entityListItemTemplate') on-enter="finishRenaming()" ) - span.dropdown( + span.dropdown.entity-menu-toggle( dropdown, ng-if="permissions.write" - ng-show="entity.selected" ) - a.dropdown-toggle(href, dropdown-toggle) + a.dropdown-toggle(href, dropdown-toggle, stop-propagation="click") i.fa.fa-chevron-down ul.dropdown-menu.dropdown-menu-right @@ -224,12 +227,14 @@ script(type='text/ng-template', id='entityListItemTemplate') href ng-click="startRenaming()" right-click="startRenaming()" + ng-show="!entity.multiSelected" ) #{translate("rename")} li a( href ng-click="openDeleteModal()" right-click="openDeleteModal()" + stop-propagation="click" ) #{translate("delete")} li.divider li @@ -261,12 +266,14 @@ script(type='text/ng-template', id='entityListItemTemplate') href ng-click="startRenaming()" right-click="startRenaming()" + ng-show="!entity.multiSelected" ) #{translate("rename")} li a( href ng-click="openDeleteModal()" right-click="openDeleteModal()" + stop-propagation="click" ) #{translate("delete")} li.divider li @@ -382,6 +389,8 @@ script(type='text/ng-template', id='deleteEntityModalTemplate') h3 #{translate("delete")} {{ entity.name }} .modal-body p !{translate("sure_you_want_to_delete")} + ul + li(ng-repeat="entity in entities") {{entity.name}} .modal-footer button.btn.btn-default( ng-disabled="state.inflight" diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 854646edfd..8a9786dd00 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -25,6 +25,7 @@ define [ "directives/onEnter" "directives/stopPropagation" "directives/rightClick" + "services/queued-http" "filters/formatDate" "main/event" "main/account-upgrade" diff --git a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee index 9ea27ae62c..c93ed4f4c0 100644 --- a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee @@ -19,6 +19,12 @@ define [ @recalculateDocList() @_bindToSocketEvents() + + @$scope.multiSelectedCount = 0 + + $(document).on "click", => + @clearMultiSelectedEntities() + $scope.$digest() _bindToSocketEvents: () -> @ide.socket.on "reciveNewDoc", (parent_folder_id, doc) => @@ -65,6 +71,7 @@ define [ @$scope.$apply () => @_deleteEntityFromScope entity @recalculateDocList() + @$scope.$emit "entity:deleted", entity @ide.socket.on "reciveEntityMove", (entity_id, folder_id) => entity = @findEntityById(entity_id) @@ -78,6 +85,55 @@ define [ @ide.fileTreeManager.forEachEntity (entity) -> entity.selected = false entity.selected = true + + toggleMultiSelectEntity: (entity) -> + entity.multiSelected = !entity.multiSelected + @$scope.multiSelectedCount = @multiSelectedCount() + + multiSelectedCount: () -> + count = 0 + @forEachEntity (entity) -> + if entity.multiSelected + count++ + return count + + getMultiSelectedEntities: () -> + entities = [] + @forEachEntity (e) -> + if e.multiSelected + entities.push e + return entities + + getMultiSelectedEntityChildNodes: () -> + entities = @getMultiSelectedEntities() + paths = {} + for entity in entities + paths[@getEntityPath(entity)] = entity + prefixes = {} + for path, entity of paths + parts = path.split("/") + if parts.length <= 1 + continue + else + # Record prefixes a/b/c.tex -> 'a' and 'a/b' + for i in [1..(parts.length - 1)] + prefixes[parts.slice(0,i).join("/")] = true + child_entities = [] + for path, entity of paths + # If the path is in the prefixes, then it's a parent folder and + # should be ignore + if !prefixes[path]? + child_entities.push entity + return child_entities + + clearMultiSelectedEntities: () -> + return if @$scope.multiSelectedCount == 0 # Be efficient, this is called a lot on 'click' + @forEachEntity (entity) -> + entity.multiSelected = false + @$scope.multiSelectedCount = 0 + + multiSelectSelectedEntity: () -> + @findSelectedEntity()?.multiSelected = true findSelectedEntity: () -> selected = null @@ -277,7 +333,7 @@ define [ deleteEntity: (entity, callback = (error) ->) -> # We'll wait for the socket.io notification to # delete from scope. - return @ide.$http { + return @ide.queuedHttp { method: "DELETE" url: "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}" headers: @@ -289,7 +345,7 @@ define [ # since that would break the tree structure. return if @_isChildFolder(entity, parent_folder) @_moveEntityInScope(entity, parent_folder) - return @ide.$http.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", { + return @ide.queuedHttp.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", { folder_id: parent_folder.id _csrf: window.csrfToken } @@ -316,8 +372,6 @@ define [ entity.deleted = true @$scope.deletedDocs.push entity - @$scope.$emit "entity:deleted", entity - _moveEntityInScope: (entity, parent_folder) -> return if entity in parent_folder.children @_deleteEntityFromScope(entity, moveToDeleted: false) diff --git a/services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee index 9c46afc967..b5c96408c7 100644 --- a/services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee +++ b/services/web/public/coffee/ide/file-tree/controllers/FileTreeEntityController.coffee @@ -2,9 +2,23 @@ define [ "base" ], (App) -> App.controller "FileTreeEntityController", ["$scope", "ide", "$modal", ($scope, ide, $modal) -> - $scope.select = () -> - ide.fileTreeManager.selectEntity($scope.entity) - $scope.$emit "entity:selected", $scope.entity + $scope.select = (e) -> + if e.ctrlKey or e.metaKey + e.stopPropagation() + initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount() + ide.fileTreeManager.toggleMultiSelectEntity($scope.entity) == 0 + if initialMultiSelectCount == 0 + # On first multi selection, also include the current active/open file. + ide.fileTreeManager.multiSelectSelectedEntity() + else + ide.fileTreeManager.selectEntity($scope.entity) + $scope.$emit "entity:selected", $scope.entity + + $scope.draggableHelper = () -> + if ide.fileTreeManager.multiSelectedCount() > 0 + return $("#{ide.fileTreeManager.multiSelectedCount()} Files") + else + return $("#{$scope.entity.name}") $scope.inputs = name: $scope.entity.name @@ -24,10 +38,15 @@ define [ $scope.startRenaming() if $scope.entity.selected $scope.openDeleteModal = () -> + if ide.fileTreeManager.multiSelectedCount() > 0 + entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes() + else + entities = [$scope.entity] $modal.open( templateUrl: "deleteEntityModalTemplate" controller: "DeleteEntityModalController" - scope: $scope + resolve: + entities: () -> entities ) $scope.$on "delete:selected", () -> @@ -35,18 +54,18 @@ define [ ] App.controller "DeleteEntityModalController", [ - "$scope", "ide", "$modalInstance", - ($scope, ide, $modalInstance) -> + "$scope", "ide", "$modalInstance", "entities" + ($scope, ide, $modalInstance, entities) -> $scope.state = inflight: false + + $scope.entities = entities $scope.delete = () -> $scope.state.inflight = true - ide.fileTreeManager - .deleteEntity($scope.entity) - .success () -> - $scope.state.inflight = false - $modalInstance.close() + for entity in $scope.entities + ide.fileTreeManager.deleteEntity(entity) + $modalInstance.close() $scope.cancel = () -> $modalInstance.dismiss('cancel') diff --git a/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee index 943168090c..7e16e7852e 100644 --- a/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee +++ b/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee @@ -9,11 +9,15 @@ define [ localStorage("folder.#{$scope.entity.id}.expanded", $scope.expanded) $scope.onDrop = (events, ui) -> - source = $(ui.draggable).scope().entity - return if !source? - # clear highlight explicitely + if ide.fileTreeManager.multiSelectedCount() + entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes() + else + entities = [$(ui.draggable).scope().entity] + for dropped_entity in entities + ide.fileTreeManager.moveEntity(dropped_entity, $scope.entity) + $scope.$digest() + # clear highlight explicitly $('.file-tree-inner .droppable-hover').removeClass('droppable-hover') - ide.fileTreeManager.moveEntity(source, $scope.entity) $scope.orderByFoldersFirst = (entity) -> # We need this here as well as in FileTreeController diff --git a/services/web/public/coffee/ide/file-tree/controllers/FileTreeRootFolderController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeRootFolderController.coffee index be72db01cf..34576c205b 100644 --- a/services/web/public/coffee/ide/file-tree/controllers/FileTreeRootFolderController.coffee +++ b/services/web/public/coffee/ide/file-tree/controllers/FileTreeRootFolderController.coffee @@ -4,7 +4,13 @@ define [ App.controller "FileTreeRootFolderController", ["$scope", "ide", ($scope, ide) -> rootFolder = $scope.rootFolder $scope.onDrop = (events, ui) -> - source = $(ui.draggable).scope().entity - return if !source? - ide.fileTreeManager.moveEntity(source, rootFolder) + if ide.fileTreeManager.multiSelectedCount() + entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes() + else + entities = [$(ui.draggable).scope().entity] + for dropped_entity in entities + ide.fileTreeManager.moveEntity(dropped_entity, rootFolder) + $scope.$digest() + # clear highlight explicitly + $('.file-tree-inner .droppable-hover').removeClass('droppable-hover') ] diff --git a/services/web/public/coffee/ide/file-tree/directives/draggable.coffee b/services/web/public/coffee/ide/file-tree/directives/draggable.coffee index 4b08a0daf6..8b37c9957e 100644 --- a/services/web/public/coffee/ide/file-tree/directives/draggable.coffee +++ b/services/web/public/coffee/ide/file-tree/directives/draggable.coffee @@ -9,6 +9,6 @@ define [ element.draggable delay: 250 opacity: 0.7 - helper: "clone" scroll: true + helper: scope.$eval(attrs.draggableHelper) } \ No newline at end of file diff --git a/services/web/public/coffee/ide/file-tree/directives/droppable.coffee b/services/web/public/coffee/ide/file-tree/directives/droppable.coffee index 890e345c23..7815a60d64 100644 --- a/services/web/public/coffee/ide/file-tree/directives/droppable.coffee +++ b/services/web/public/coffee/ide/file-tree/directives/droppable.coffee @@ -9,6 +9,7 @@ define [ element.droppable greedy: true hoverClass: "droppable-hover" + tolerance: "pointer" accept: attrs.accept drop: scope.$eval(attrs.onDropCallback) } \ No newline at end of file diff --git a/services/web/public/coffee/ide/services/ide.coffee b/services/web/public/coffee/ide/services/ide.coffee index 1410e3db33..6741f041ba 100644 --- a/services/web/public/coffee/ide/services/ide.coffee +++ b/services/web/public/coffee/ide/services/ide.coffee @@ -3,9 +3,10 @@ define [ ], (App) -> # We create and provide this as service so that we can access the global ide # from within other parts of the angular app. - App.factory "ide", ["$http", "$modal", ($http, $modal) -> + App.factory "ide", ["$http", "queuedHttp", "$modal", ($http, queuedHttp, $modal) -> ide = {} ide.$http = $http + ide.queuedHttp = queuedHttp @recentEvents = [] ide.pushEvent = (type, meta = {}) => diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index dd0814740e..ad3e8d8a36 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -25,6 +25,7 @@ define [ "directives/onEnter" "directives/selectAll" "directives/maxHeight" + "services/queued-http" "filters/formatDate" "__MAIN_CLIENTSIDE_INCLUDES__" ], () -> diff --git a/services/web/public/coffee/main/project-list/index.coffee b/services/web/public/coffee/main/project-list/index.coffee index 936228b546..05b61ae57f 100644 --- a/services/web/public/coffee/main/project-list/index.coffee +++ b/services/web/public/coffee/main/project-list/index.coffee @@ -3,6 +3,6 @@ define [ "main/project-list/modal-controllers" "main/project-list/tag-controllers" "main/project-list/notifications-controller" - "main/project-list/queued-http" "main/project-list/left-hand-menu-promo-controller" + "services/queued-http" ], () -> \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list/queued-http.coffee b/services/web/public/coffee/services/queued-http.coffee similarity index 99% rename from services/web/public/coffee/main/project-list/queued-http.coffee rename to services/web/public/coffee/services/queued-http.coffee index 0d7823b597..c4c6862fae 100644 --- a/services/web/public/coffee/main/project-list/queued-http.coffee +++ b/services/web/public/coffee/services/queued-http.coffee @@ -1,7 +1,6 @@ define [ "base" ], (App) -> - App.factory "queuedHttp", ($http, $q) -> pendingRequests = [] inflight = false diff --git a/services/web/public/js/libs/jquery-layout.js b/services/web/public/js/libs/jquery-layout.js index e5bec01cb8..43ecc886c1 100644 --- a/services/web/public/js/libs/jquery-layout.js +++ b/services/web/public/js/libs/jquery-layout.js @@ -1,285 +1,25 @@ - // (function(LST) { - - // LST.rethrow = false; - - // var currentTraceError = null; - - // var filename = new Error().stack.split("\n")[1].match(/^ at ((?:\w+:\/\/)?[^:]+)/)[1]; - // function filterInternalFrames(frames) { - // return frames.split("\n").filter(function(frame) { return frame.indexOf(filename) < 0; }).join("\n"); - // } - - // Error.prepareStackTrace = function(error, structuredStackTrace) { - // if (!error.__cachedTrace) { - // error.__cachedTrace = filterInternalFrames(FormatStackTrace(error, structuredStackTrace)); - // if (!has.call(error, "__previous")) { - // var previous = currentTraceError; - // while (previous) { - // var previousTrace = previous.stack; - // error.__cachedTrace += "\n----------------------------------------\n" + - // " at " + previous.__location + "\n" + - // previousTrace.substring(previousTrace.indexOf("\n") + 1); - // previous = previous.__previous; - // } - // } - // } - // return error.__cachedTrace; - // } - - // var slice = Array.prototype.slice; - // var has = Object.prototype.hasOwnProperty; - - // // Takes an object, a property name for the callback function to wrap, and an argument position - // // and overwrites the function with a wrapper that captures the stack at the time of callback registration - // function wrapRegistrationFunction(object, property, callbackArg) { - // if (typeof object[property] !== "function") { - // console.error("(long-stack-traces) Object", object, "does not contain function", property); - // return; - // } - // if (!has.call(object, property)) { - // console.warn("(long-stack-traces) Object", object, "does not directly contain function", property); - // } - - // // TODO: better source position detection - // var sourcePosition = (object.constructor.name || Object.prototype.toString.call(object)) + "." + property; - - // // capture the original registration function - // var fn = object[property]; - // // overwrite it with a wrapped registration function that modifies the supplied callback argument - // object[property] = function() { - // // replace the callback argument with a wrapped version that captured the current stack trace - // arguments[callbackArg] = makeWrappedCallback(arguments[callbackArg], sourcePosition); - // // call the original registration function with the modified arguments - // return fn.apply(this, arguments); - // } - - // // check that the registration function was indeed overwritten - // if (object[property] === fn) - // console.warn("(long-stack-traces) Couldn't replace ", property, "on", object); - // } - - // // Takes a callback function and name, and captures a stack trace, returning a new callback that restores the stack frame - // // This function adds a single function call overhead during callback registration vs. inlining it in wrapRegistationFunction - // function makeWrappedCallback(callback, frameLocation) { - // // add a fake stack frame. we can't get a real one since we aren't inside the original function - // var traceError = new Error(); - // traceError.__location = frameLocation; - // traceError.__previous = currentTraceError; - // return function() { - // // if (currentTraceError) { - // // FIXME: This shouldn't normally happen, but it often does. Do we actually need a stack instead? - // // console.warn("(long-stack-traces) Internal Error: currentTrace already set."); - // // } - // // restore the trace - // currentTraceError = traceError; - // try { - // return callback.apply(this, arguments); - // } catch (e) { - // console.error("Uncaught " + e.stack); - // if (LST.rethrow) - // throw ""; // TODO: throw the original error, or undefined? - // } finally { - // // clear the trace so we can check that none is set above. - // // TODO: could we remove this for slightly better performace? - // currentTraceError = null; - // } - // } - // } - - // // Chrome - // if (typeof window !== "undefined") { - // wrapRegistrationFunction(window.constructor.prototype, "setTimeout", 0); - // wrapRegistrationFunction(window.constructor.prototype, "setInterval", 0); - - // [ - // window.Node.prototype, - // window.MessagePort.prototype, - // window.SVGElementInstance.prototype, - // window.WebSocket.prototype, - // window.XMLHttpRequest.prototype, - // window.EventSource.prototype, - // window.XMLHttpRequestUpload.prototype, - // window.SharedWorker.prototype.__proto__, - // window.constructor.prototype, - // window.applicationCache.constructor.prototype - // ].forEach(function(object) { - // wrapRegistrationFunction(object, "addEventListener", 1); - // }); - - // // this actually captures the stack when "send" is called, which isn't ideal, - // // but it's the best we can do without hooking onreadystatechange assignments - // var _send = XMLHttpRequest.prototype.send; - // XMLHttpRequest.prototype.send = function() { - // this.onreadystatechange = makeWrappedCallback(this.onreadystatechange, "onreadystatechange"); - // return _send.apply(this, arguments); - // } - - // // FIXME: experimental XHR wrapper for hooking onreadystatechange - // // Based on https://gist.github.com/796032 - // // var _XMLHttpRequest = XMLHttpRequest; - // // XMLHttpRequest = function () { - // // Object.defineProperty(this, "onreadystatechange", { - // // get: function() { - // // return this.__onreadystatechange; - // // }, - // // set: function(onreadystatechange) { - // // if (this.__onreadystatechange && typeof this.__onreadystatechange.call === "function") - // // this.removeEventListener("readystatechange", this.__onreadystatechange); - // // this.__onreadystatechange = makeWrappedCallback(onreadystatechange, "onreadystatechange"); - // // if (this.__onreadystatechange && typeof this.__onreadystatechange.call === "function") - // // this.addEventListener("readystatechange", this.__onreadystatechange); - // // }, - // // enumerable: true - // // }); - // // Object.defineProperty(this, "__onreadystatechange", { - // // value: null, - // // writable: true, - // // enumerable: false - // // }); - // // } - // // XMLHttpRequest.prototype = new _XMLHttpRequest(); - // } - // // Node.js - // else if (typeof process !== "undefined") { - // LST.rethrow = true; - - // var global = (function() { return this; })(); - // wrapRegistrationFunction(global, "setTimeout", 0); - // wrapRegistrationFunction(global, "setInterval", 0); - // wrapRegistrationFunction(global, "setImmediate", 0); - - // var EventEmitter = require('events').EventEmitter; - // wrapRegistrationFunction(EventEmitter.prototype, "addListener", 1); - // wrapRegistrationFunction(EventEmitter.prototype, "on", 1); - - // wrapRegistrationFunction(process, "nextTick", 0); - // } - - // // Copyright 2006-2008 the V8 project authors. All rights reserved. - // // Redistribution and use in source and binary forms, with or without - // // modification, are permitted provided that the following conditions are - // // met: - // // - // // * Redistributions of source code must retain the above copyright - // // notice, this list of conditions and the following disclaimer. - // // * Redistributions in binary form must reproduce the above - // // copyright notice, this list of conditions and the following - // // disclaimer in the documentation and/or other materials provided - // // with the distribution. - // // * Neither the name of Google Inc. nor the names of its - // // contributors may be used to endorse or promote products derived - // // from this software without specific prior written permission. - // // - // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - // // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - // // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - // // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - // // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - // // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - // // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - // // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - // // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - // // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - // // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - // function FormatStackTrace(error, frames) { - // var lines = []; - // try { - // lines.push(error.toString()); - // } catch (e) { - // try { - // lines.push(""); - // } catch (ee) { - // lines.push(""); - // } - // } - // for (var i = 0; i < frames.length; i++) { - // var frame = frames[i]; - // var line; - // try { - // line = FormatSourcePosition(frame); - // } catch (e) { - // try { - // line = ""; - // } catch (ee) { - // // Any code that reaches this point is seriously nasty! - // line = ""; - // } - // } - // lines.push(" at " + line); - // } - // return lines.join("\n"); - // } - - // function FormatSourcePosition(frame) { - // var fileLocation = ""; - // if (frame.isNative()) { - // fileLocation = "native"; - // } else if (frame.isEval()) { - // fileLocation = "eval at " + frame.getEvalOrigin(); - // } else { - // var fileName = frame.getFileName(); - // if (fileName) { - // fileLocation += fileName; - // var lineNumber = frame.getLineNumber(); - // if (lineNumber != null) { - // fileLocation += ":" + lineNumber; - // var columnNumber = frame.getColumnNumber(); - // if (columnNumber) { - // fileLocation += ":" + columnNumber; - // } - // } - // } - // } - // if (!fileLocation) { - // fileLocation = "unknown source"; - // } - // var line = ""; - // var functionName = frame.getFunction().name; - // var addPrefix = true; - // var isConstructor = frame.isConstructor(); - // var isMethodCall = !(frame.isToplevel() || isConstructor); - // if (isMethodCall) { - // var methodName = frame.getMethodName(); - // line += frame.getTypeName() + "."; - // if (functionName) { - // line += functionName; - // if (methodName && (methodName != functionName)) { - // line += " [as " + methodName + "]"; - // } - // } else { - // line += methodName || ""; - // } - // } else if (isConstructor) { - // line += "new " + (functionName || ""); - // } else if (functionName) { - // line += functionName; - // } else { - // line += fileLocation; - // addPrefix = false; - // } - // if (addPrefix) { - // line += " (" + fileLocation + ")"; - // } - // return line; - // } - // })(typeof exports !== "undefined" ? exports : {}); - -/*! jQuery UI - v1.10.3 - 2013-10-24 +/*! jQuery UI - v1.11.4 - 2016-02-10 * http://jqueryui.com -* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js -* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ +* Includes: core.js, widget.js, mouse.js, draggable.js, droppable.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ -(function( $, undefined ) { +/*! + * jQuery UI Core 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/category/ui-core/ + */ -var uuid = 0, - runiqueId = /^ui-id-\d+$/; // $.ui might exist from components with no dependencies, e.g., $.ui.position $.ui = $.ui || {}; $.extend( $.ui, { - version: "1.10.3", + version: "1.11.4", keyCode: { BACKSPACE: 8, @@ -291,12 +31,6 @@ $.extend( $.ui, { ESCAPE: 27, HOME: 36, LEFT: 37, - NUMPAD_ADD: 107, - NUMPAD_DECIMAL: 110, - NUMPAD_DIVIDE: 111, - NUMPAD_ENTER: 108, - NUMPAD_MULTIPLY: 106, - NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, @@ -309,77 +43,36 @@ $.extend( $.ui, { // plugins $.fn.extend({ - focus: (function( orig ) { - return function( delay, fn ) { - return typeof delay === "number" ? - this.each(function() { - var elem = this; - setTimeout(function() { - $( elem ).focus(); - if ( fn ) { - fn.call( elem ); - } - }, delay ); - }) : - orig.apply( this, arguments ); - }; - })( $.fn.focus ), - - scrollParent: function() { - var scrollParent; - if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { - scrollParent = this.parents().filter(function() { - return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); - }).eq(0); - } else { - scrollParent = this.parents().filter(function() { - return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); - }).eq(0); - } - - return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; - }, - - zIndex: function( zIndex ) { - if ( zIndex !== undefined ) { - return this.css( "zIndex", zIndex ); - } - - if ( this.length ) { - var elem = $( this[ 0 ] ), position, value; - while ( elem.length && elem[ 0 ] !== document ) { - // Ignore z-index if position is set to a value where z-index is ignored by the browser - // This makes behavior of this function consistent across browsers - // WebKit always returns auto if the element is positioned - position = elem.css( "position" ); - if ( position === "absolute" || position === "relative" || position === "fixed" ) { - // IE returns 0 when zIndex is not specified - // other browsers return a string - // we ignore the case of nested elements with an explicit value of 0 - //
- value = parseInt( elem.css( "zIndex" ), 10 ); - if ( !isNaN( value ) && value !== 0 ) { - return value; - } + scrollParent: function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; } - elem = elem.parent(); - } - } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); + }).eq( 0 ); - return 0; + return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; }, - uniqueId: function() { - return this.each(function() { - if ( !this.id ) { - this.id = "ui-id-" + (++uuid); - } - }); - }, + uniqueId: (function() { + var uuid = 0; + + return function() { + return this.each(function() { + if ( !this.id ) { + this.id = "ui-id-" + ( ++uuid ); + } + }); + }; + })(), removeUniqueId: function() { return this.each(function() { - if ( runiqueId.test( this.id ) ) { + if ( /^ui-id-\d+$/.test( this.id ) ) { $( this ).removeAttr( "id" ); } }); @@ -396,10 +89,10 @@ function focusable( element, isTabIndexNotNaN ) { if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } - img = $( "img[usemap=#" + mapName + "]" )[0]; + img = $( "img[usemap='#" + mapName + "']" )[ 0 ]; return !!img && visible( img ); } - return ( /input|select|textarea|button|object/.test( nodeName ) ? + return ( /^(input|select|textarea|button|object)$/.test( nodeName ) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : @@ -507,94 +200,137 @@ if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { })( $.fn.removeData ); } - - - - // deprecated $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); -$.support.selectstart = "onselectstart" in document.createElement( "div" ); $.fn.extend({ - disableSelection: function() { - return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + - ".ui-disableSelection", function( event ) { + focus: (function( orig ) { + return function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + orig.apply( this, arguments ); + }; + })( $.fn.focus ), + + disableSelection: (function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; + + return function() { + return this.bind( eventType + ".ui-disableSelection", function( event ) { event.preventDefault(); }); - }, + }; + })(), enableSelection: function() { return this.unbind( ".ui-disableSelection" ); - } -}); - -$.extend( $.ui, { - // $.ui.plugin is deprecated. Use $.widget() extensions instead. - plugin: { - add: function( module, option, set ) { - var i, - proto = $.ui[ module ].prototype; - for ( i in set ) { - proto.plugins[ i ] = proto.plugins[ i ] || []; - proto.plugins[ i ].push( [ option, set[ i ] ] ); - } - }, - call: function( instance, name, args ) { - var i, - set = instance.plugins[ name ]; - if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { - return; - } - - for ( i = 0; i < set.length; i++ ) { - if ( instance.options[ set[ i ][ 0 ] ] ) { - set[ i ][ 1 ].apply( instance.element, args ); - } - } - } }, - // only used by resizable - hasScroll: function( el, a ) { - - //If overflow is hidden, the element might have extra content, but the user wants to hide it - if ( $( el ).css( "overflow" ) === "hidden") { - return false; + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); } - var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", - has = false; - - if ( el[ scroll ] > 0 ) { - return true; + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + //
+ value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } } - // TODO: determine which cases actually cause this to happen - // if the element doesn't have the scroll set, see if it's possible to - // set the scroll - el[ scroll ] = 1; - has = ( el[ scroll ] > 0 ); - el[ scroll ] = 0; - return has; + return 0; } }); -})( jQuery ); -(function( $, undefined ) { +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +$.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; -var uuid = 0, - slice = Array.prototype.slice, - _cleanData = $.cleanData; -$.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - try { - $( elem ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } } - _cleanData( elems ); }; + +/*! + * jQuery UI Widget 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ + + +var widget_uuid = 0, + widget_slice = Array.prototype.slice; + +$.cleanData = (function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; (elem = elems[i]) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +})( $.cleanData ); + $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified @@ -678,7 +414,7 @@ $.widget = function( name, base, prototype ) { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based - widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name + widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, @@ -706,10 +442,12 @@ $.widget = function( name, base, prototype ) { } $.widget.bridge( name, constructor ); + + return constructor; }; $.widget.extend = function( target ) { - var input = slice.call( arguments, 1 ), + var input = widget_slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, @@ -738,18 +476,17 @@ $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", - args = slice.call( arguments, 1 ), + args = widget_slice.call( arguments, 1 ), returnValue = this; - // allow multiple hashes to be passed on init - options = !isMethodCall && args.length ? - $.widget.extend.apply( null, [ options ].concat(args) ) : - options; - if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); + if ( options === "instance" ) { + returnValue = instance; + return false; + } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); @@ -766,10 +503,19 @@ $.widget.bridge = function( name, object ) { } }); } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat(args) ); + } + this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { - instance.option( options || {} )._init(); + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } } else { $.data( this, fullName, new object( options, this ) ); } @@ -796,12 +542,8 @@ $.Widget.prototype = { _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); - this.uuid = uuid++; + this.uuid = widget_uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; - this.options = $.widget.extend( {}, - this.options, - this._getCreateOptions(), - options ); this.bindings = $(); this.hoverable = $(); @@ -824,6 +566,11 @@ $.Widget.prototype = { this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); @@ -839,9 +586,6 @@ $.Widget.prototype = { // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) - // 1.9 BC for #7810 - // TODO remove dual storage - .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 @@ -887,12 +631,12 @@ $.Widget.prototype = { curOption = curOption[ parts[ i ] ]; } key = parts.pop(); - if ( value === undefined ) { + if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { - if ( value === undefined ) { + if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; @@ -917,20 +661,23 @@ $.Widget.prototype = { if ( key === "disabled" ) { this.widget() - .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) - .attr( "aria-disabled", value ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); + .toggleClass( this.widgetFullName + "-disabled", !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } } return this; }, enable: function() { - return this._setOption( "disabled", false ); + return this._setOptions({ disabled: false }); }, disable: function() { - return this._setOption( "disabled", true ); + return this._setOptions({ disabled: true }); }, _on: function( suppressDisabledCheck, element, handlers ) { @@ -950,7 +697,6 @@ $.Widget.prototype = { element = this.element; delegateElement = this.widget(); } else { - // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } @@ -975,7 +721,7 @@ $.Widget.prototype = { handler.guid || handlerProxy.guid || $.guid++; } - var match = event.match( /^(\w+)\s*(.*)$/ ), + var match = event.match( /^([\w:-]*)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { @@ -987,8 +733,14 @@ $.Widget.prototype = { }, _off: function( element, eventName ) { - eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); }, _delay: function( handler, delay ) { @@ -1090,16 +842,28 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { }; }); -})( jQuery ); -(function( $, undefined ) { +var widget = $.widget; + + +/*! + * jQuery UI Mouse 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/mouse/ + */ + var mouseHandled = false; $( document ).mouseup( function() { mouseHandled = false; }); -$.widget("ui.mouse", { - version: "1.10.3", +var mouse = $.widget("ui.mouse", { + version: "1.11.4", options: { cancel: "input,textarea,button,select,option", distance: 1, @@ -1109,10 +873,10 @@ $.widget("ui.mouse", { var that = this; this.element - .bind("mousedown."+this.widgetName, function(event) { + .bind("mousedown." + this.widgetName, function(event) { return that._mouseDown(event); }) - .bind("click."+this.widgetName, function(event) { + .bind("click." + this.widgetName, function(event) { if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { $.removeData(event.target, that.widgetName + ".preventClickEvent"); event.stopImmediatePropagation(); @@ -1126,17 +890,21 @@ $.widget("ui.mouse", { // TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { - this.element.unbind("."+this.widgetName); + this.element.unbind("." + this.widgetName); if ( this._mouseMoveDelegate ) { - $(document) - .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) - .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + this.document + .unbind("mousemove." + this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup." + this.widgetName, this._mouseUpDelegate); } }, _mouseDown: function(event) { // don't let more than one widget handle mouseStart - if( mouseHandled ) { return; } + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; // we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event)); @@ -1179,9 +947,10 @@ $.widget("ui.mouse", { this._mouseUpDelegate = function(event) { return that._mouseUp(event); }; - $(document) - .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) - .bind("mouseup."+this.widgetName, this._mouseUpDelegate); + + this.document + .bind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .bind( "mouseup." + this.widgetName, this._mouseUpDelegate ); event.preventDefault(); @@ -1190,9 +959,23 @@ $.widget("ui.mouse", { }, _mouseMove: function(event) { - // IE mouseup check - mouseup happened when mouse was out of window - if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { - return this._mouseUp(event); + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + return this._mouseUp( event ); + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; } if (this._mouseStarted) { @@ -1210,9 +993,9 @@ $.widget("ui.mouse", { }, _mouseUp: function(event) { - $(document) - .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) - .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + this.document + .unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .unbind( "mouseup." + this.widgetName, this._mouseUpDelegate ); if (this._mouseStarted) { this._mouseStarted = false; @@ -1224,6 +1007,7 @@ $.widget("ui.mouse", { this._mouseStop(event); } + mouseHandled = false; return false; }, @@ -1246,11 +1030,21 @@ $.widget("ui.mouse", { _mouseCapture: function(/* event */) { return true; } }); -})(jQuery); -(function( $, undefined ) { + +/*! + * jQuery UI Draggable 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/draggable/ + */ + $.widget("ui.draggable", $.ui.mouse, { - version: "1.10.3", + version: "1.11.4", widgetEventPrefix: "drag", options: { addClasses: true, @@ -1285,8 +1079,8 @@ $.widget("ui.draggable", $.ui.mouse, { }, _create: function() { - if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { - this.element[0].style.position = "relative"; + if ( this.options.helper === "original" ) { + this._setPositionRelative(); } if (this.options.addClasses){ this.element.addClass("ui-draggable"); @@ -1294,20 +1088,34 @@ $.widget("ui.draggable", $.ui.mouse, { if (this.options.disabled){ this.element.addClass("ui-draggable-disabled"); } + this._setHandleClassName(); this._mouseInit(); + }, + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "handle" ) { + this._removeHandleClassName(); + this._setHandleClassName(); + } }, _destroy: function() { + if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { + this.destroyOnClear = true; + return; + } this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); + this._removeHandleClassName(); this._mouseDestroy(); }, _mouseCapture: function(event) { - var o = this.options; + this._blurActiveElement( event ); + // among others, prevent a drag on a resizable-handle if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { return false; @@ -1319,20 +1127,54 @@ $.widget("ui.draggable", $.ui.mouse, { return false; } - $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { - $("
") - .css({ - width: this.offsetWidth+"px", height: this.offsetHeight+"px", - position: "absolute", opacity: "0.001", zIndex: 1000 - }) - .css($(this).offset()) - .appendTo("body"); - }); + this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); return true; }, + _blockFrames: function( selector ) { + this.iframeBlocks = this.document.find( selector ).map(function() { + var iframe = $( this ); + + return $( "
" ) + .css( "position", "absolute" ) + .appendTo( iframe.parent() ) + .outerWidth( iframe.outerWidth() ) + .outerHeight( iframe.outerHeight() ) + .offset( iframe.offset() )[ 0 ]; + }); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _blurActiveElement: function( event ) { + var document = this.document[ 0 ]; + + // Only need to blur if the event occurred on the draggable itself, see #10527 + if ( !this.handleElement.is( event.target ) ) { + return; + } + + // support: IE9 + // IE9 throws an "Unspecified error" accessing document.activeElement from an