From dd0db6cd788ed37a234f942a9ec8f85f5be30b16 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 11 Feb 2016 10:48:05 +0100 Subject: [PATCH] Reduce taiga weight - improve uglify - remove jquery ui - add dragula - new autocomplete --- app/coffee/app.coffee | 3 + .../modules/admin/project-values.coffee | 25 +- app/coffee/modules/backlog/main.coffee | 44 +- app/coffee/modules/backlog/sortable.coffee | 232 +- app/coffee/modules/common/tags.coffee | 55 +- app/coffee/modules/kanban/main.coffee | 4 - app/coffee/modules/kanban/sortable.coffee | 40 +- app/coffee/modules/taskboard/main.coffee | 2 - app/coffee/modules/taskboard/sortable.coffee | 43 +- app/js/dom-autoscroller.js | 573 ++ app/js/dragula-drag-multiple.js | 221 + app/js/jquery-ui.drag-multiple-custom.js | 161 - app/js/jquery.ui.git-custom.js | 7033 ----------------- app/js/jquery.ui.touch-punch.min.js | 11 - app/modules/attachments/attachments.scss | 24 +- .../attachments-sortable.directive.coffee | 35 +- .../components/sort-projects.directive.coffee | 28 +- .../projects/listing/styles/project-list.scss | 16 +- .../services/app-meta.service.spec.coffee | 2 + .../services/scope-event.service.coffee | 54 - .../services/scope-event.service.spec.coffee | 102 - .../includes/components/backlog-row.jade | 2 +- .../includes/modules/kanban-table.jade | 3 +- app/partials/includes/modules/sprint.jade | 2 +- .../includes/modules/taskboard-table.jade | 6 +- app/styles/components/kanban-task.scss | 6 +- app/styles/components/taskboard-task.scss | 5 +- app/styles/core/elements.scss | 36 + app/styles/layout/backlog.scss | 22 + app/styles/modules/backlog/backlog-table.scss | 10 +- app/styles/modules/backlog/sprints.scss | 18 +- .../modules/backlog/taskboard-table.scss | 9 +- app/styles/modules/common/colors-table.scss | 9 + app/styles/vendor/jquery.mCustomScrollbar.css | 1272 --- bower.json | 15 +- conf.e2e.js | 6 +- gulp-utils.js | 86 + gulpfile.js | 58 +- package.json | 15 +- 39 files changed, 1303 insertions(+), 8985 deletions(-) create mode 100644 app/js/dom-autoscroller.js create mode 100644 app/js/dragula-drag-multiple.js delete mode 100644 app/js/jquery-ui.drag-multiple-custom.js delete mode 100644 app/js/jquery.ui.git-custom.js delete mode 100644 app/js/jquery.ui.touch-punch.min.js delete mode 100644 app/modules/services/scope-event.service.coffee delete mode 100644 app/modules/services/scope-event.service.spec.coffee delete mode 100644 app/styles/vendor/jquery.mCustomScrollbar.css diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 8d0b88de..ac0d17ed 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -601,6 +601,9 @@ i18nInit = (lang, $translate) -> # i18n - moment.js moment.locale(lang) + if (lang != 'en') # en is the default, the file doesn't exist + ljs.load "/#{window._version}/locales/moment-locales/" + lang + ".js" + # i18n - checksley.js messages = { defaultMessage: $translate.instant("COMMON.FORM_ERRORS.DEFAULT_MESSAGE") diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index e4bef2be..da753716 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -137,22 +137,31 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra itemEl = null tdom = $el.find(".sortable") - tdom.sortable({ - handle: ".row.table-main.visualization", - dropOnEmpty: true - connectWith: ".project-values-body" - revert: 400 - axis: "y" + drake = dragula([tdom[0]], { + direction: 'vertical', + copySortSource: false, + copy: false, + mirrorContainer: tdom[0], + moves: (item) -> return $(item).is('div[tg-bind-scope]') }) - tdom.on "sortstop", (event, ui) -> - itemEl = ui.item + drake.on 'dragend', (item) -> + itemEl = $(item) itemValue = itemEl.scope().value itemIndex = itemEl.index() $scope.$broadcast("admin:project-values:move", itemValue, itemIndex) + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) + $scope.$on "$destroy", -> $el.off() + drake.destroy() ## Value Link diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 3031fc8f..e74ec6c1 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -139,8 +139,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @rootscope.$broadcast("filters:update") @scope.$on("sprint:us:move", @.moveUs) - @scope.$on("sprint:us:moved", @.loadSprints) - @scope.$on("sprint:us:moved", @.loadProjectStats) + @scope.$on "sprint:us:moved", () => + @.loadSprints() + @.loadProjectStats() @scope.$on("backlog:load-closed-sprints", @.loadClosedSprints) @scope.$on("backlog:unload-closed-sprints", @.unloadClosedSprints) @@ -390,8 +391,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # Persist in bulk all affected # userstories with order change @rs.userstories.bulkUpdateBacklogOrder(project, data).then => - for us in usList - @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId) + @rootscope.$broadcast("sprint:us:moved") # For sprint else @@ -402,8 +402,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # Persist in bulk all affected # userstories with order change @rs.userstories.bulkUpdateSprintOrder(project, data).then => - for us in usList - @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId) + @rootscope.$broadcast("sprint:us:moved") return promise @@ -430,7 +429,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F items = @.resortUserStories(@scope.userstories, "backlog_order") data = @.prepareBulkUpdateData(items, "backlog_order") return @rs.userstories.bulkUpdateBacklogOrder(us.project, data).then => - @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId) + @rootscope.$broadcast("sprint:us:moved") if movedFromClosedSprint @rootscope.$broadcast("backlog:load-closed-sprints") @@ -443,17 +442,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # From backlog to sprint if oldSprintId == null us.milestone = newSprintId for us in usList + args = [newUsIndex, 0].concat(usList) - @scope.$apply => - args = [newUsIndex, 0].concat(usList) + # Add moving us to sprint user stories list + Array.prototype.splice.apply(newSprint.user_stories, args) - # Add moving us to sprint user stories list - Array.prototype.splice.apply(newSprint.user_stories, args) - - # Remove moving us from backlog userstories lists. - for us, key in usList - r = @scope.userstories.indexOf(us) - @scope.userstories.splice(r, 1) + # Remove moving us from backlog userstories lists. + for us, key in usList + r = @scope.userstories.indexOf(us) + @scope.userstories.splice(r, 1) # From sprint to sprint else @@ -470,21 +467,20 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F r = sprint.user_stories.indexOf(us) sprint.user_stories.splice(r, 1) - # Persist the milestone change of userstory + #Persist the milestone change of userstory promises = _.map usList, (us) => @repo.save(us) - # Rehash userstories order field - # and persist in bulk all changes. + #Rehash userstories order field + #and persist in bulk all changes. promise = @q.all(promises).then => items = @.resortUserStories(newSprint.user_stories, "sprint_order") data = @.prepareBulkUpdateData(items, "sprint_order") @rs.userstories.bulkUpdateSprintOrder(project, data).then (result) => - @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId) + @rootscope.$broadcast("sprint:us:moved") @rs.userstories.bulkUpdateBacklogOrder(project, data).then => - for us in usList - @rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId) + @rootscope.$broadcast("sprint:us:moved") if movedToClosedSprint || movedFromClosedSprint @scope.$broadcast("backlog:load-closed-sprints") @@ -680,7 +676,7 @@ BacklogDirective = ($repo, $rootscope, $translate) -> return _.map(rowElements, (x) -> angular.element(x)) $scope.$on("userstories:loaded", reloadDoomLine) - $scope.$watch "stats", reloadDoomLine + $scope.$watch("stats", reloadDoomLine) ## Move to current sprint link @@ -835,8 +831,6 @@ BacklogDirective = ($repo, $rootscope, $translate) -> linkFilters($scope, $el, $attrs, $ctrl) linkDoomLine($scope, $el, $attrs, $ctrl) - $el.find(".backlog-table-body").disableSelection() - filters = $ctrl.getUrlFilters() if filters.status || filters.tags || diff --git a/app/coffee/modules/backlog/sortable.coffee b/app/coffee/modules/backlog/sortable.coffee index e674049f..9be36c54 100644 --- a/app/coffee/modules/backlog/sortable.coffee +++ b/app/coffee/modules/backlog/sortable.coffee @@ -38,187 +38,117 @@ module = angular.module("taigaBacklog") ############################################################################# deleteElement = (el) -> - el.scope().$destroy() - el.off() - el.remove() + $(el).scope().$destroy() + $(el).off() + $(el).remove() BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> - # Notes about jquery bug: - # http://stackoverflow.com/questions/5791886/jquery-draggable-shows- - # helper-in-wrong-place-when-scrolled-down-page - link = ($scope, $el, $attrs) -> - getUsIndex = (us) => - return $(us).index(".backlog-table-body .row") - bindOnce $scope, "project", (project) -> # If the user has not enough permissions we don't enable the sortable if not (project.my_permissions.indexOf("modify_us") > -1) return + initIsBacklog = false + filterError = -> text = $translate.instant("BACKLOG.SORTABLE_FILTER_ERROR") $tgConfirm.notify("error", text) - $el.sortable({ - items: ".us-item-row", - cancel: ".popover" - connectWith: ".sprint" - dropOnEmpty: true - placeholder: "row us-item-row us-item-drag sortable-placeholder" - scroll: true - disableHorizontalScroll: true - # A consequence of length of backlog user story item - # the default tolerance ("intersection") not works properly. - tolerance: "pointer" - # Revert on backlog is disabled bacause it works bad. Something - # on the current taiga backlog structure or style makes jquery ui - # works unexpectly (in some circumstances calculates wrong - # position for revert). - revert: false - start: () -> - $(document.body).addClass("drag-active") - stop: () -> - $(document.body).removeClass("drag-active") + drake = dragula([$el[0], $('.empty-backlog')[0]], { + copySortSource: false, + copy: false, + isContainer: (el) -> return el.classList.contains('sprint-table'), + moves: (item) -> + if !$(item).hasClass('row') + return false - if $el.hasClass("active-filters") - $el.sortable("cancel") + # it doesn't move is the filter is open + parent = $(item).parent() + initIsBacklog = parent.hasClass('backlog-table-body') + + if initIsBacklog && $el.hasClass("active-filters") filterError() + return false + + return true }) - $el.on "multiplesortreceive", (event, ui) -> - if $el.hasClass("active-filters") - ui.source.sortable("cancel") - filterError() + drake.on 'drag', (item, container) -> + parent = $(item).parent() + initIsBacklog = parent.hasClass('backlog-table-body') - return + $(document.body).addClass("drag-active") - itemUs = ui.item.scope().us - itemIndex = getUsIndex(ui.item) + isChecked = $(item).find("input[type='checkbox']").is(":checked") - deleteElement(ui.item) + window.dragMultiple.start(item, container) - $scope.$emit("sprint:us:move", [itemUs], itemIndex, null) - ui.item.find('a').removeClass('noclick') + drake.on 'cloned', (item) -> + $(item).addClass('backlog-us-mirror') - $el.on "multiplesortstop", (event, ui) -> - # When parent not exists, do nothing - if $(ui.items[0]).parent().length == 0 - return + drake.on 'dragend', (item) -> + $('.doom-line').remove() - if $el.hasClass("active-filters") - return + parent = $(item).parent() + isBacklog = parent.hasClass('backlog-table-body') || parent.hasClass('empty-backlog') - items = _.sortBy ui.items, (item) -> - return $(item).index() + sameContainer = (initIsBacklog == isBacklog) - index = _.min _.map items, (item) -> - return getUsIndex(item) + dragMultipleItems = window.dragMultiple.stop() - us = _.map items, (item) -> - item = $(item) - itemUs = item.scope().us + $(document.body).removeClass("drag-active") - # HACK: setTimeout prevents that firefox click - # event fires just after drag ends - setTimeout ( => - item.find('a').removeClass('noclick') - ), 300 + items = $(item).parent().find('.row') - return itemUs + sprint = null - $scope.$emit("sprint:us:move", us, index, null) + firstElement = if dragMultipleItems.length then dragMultipleItems[0] else item - $el.on "sortstart", (event, ui) -> - ui.item.find('a').addClass('noclick') + if isBacklog + index = $(firstElement).index(".backlog-table-body .row") + else + index = $(firstElement).index() + sprint = parent.scope().sprint.id - $scope.$on "$destroy", -> - $el.off() - - return {link: link} - -BacklogEmptySortableDirective = ($repo, $rs, $rootscope) -> - # Notes about jquery bug: - # http://stackoverflow.com/questions/5791886/jquery-draggable-shows- - # helper-in-wrong-place-when-scrolled-down-page - - link = ($scope, $el, $attrs) -> - bindOnce $scope, "project", (project) -> - # If the user has not enough permissions we don't enable the sortable - if project.my_permissions.indexOf("modify_us") > -1 - $el.sortable({ - items: ".us-item-row", - dropOnEmpty: true - }) - - $el.on "sortreceive", (event, ui) -> - itemUs = ui.item.scope().us - itemIndex = ui.item.index() - - deleteElement(ui.item) - $scope.$emit("sprint:us:move", [itemUs], itemIndex, null) - - ui.item.find('a').removeClass('noclick') - - $scope.$on "$destroy", -> - $el.off() - - return {link: link} - - -SprintSortableDirective = ($repo, $rs, $rootscope) -> - link = ($scope, $el, $attrs) -> - bindOnce $scope, "project", (project) -> - # If the user has not enough permissions we don't enable the sortable - if project.my_permissions.indexOf("modify_us") > -1 - $el.sortable({ - scroll: true - dropOnEmpty: true - items: ".sprint-table .milestone-us-item-row" - disableHorizontalScroll: true - connectWith: ".sprint,.backlog-table-body,.empty-backlog" - placeholder: "row us-item-row sortable-placeholder" - forcePlaceholderSize:true - }) - - $el.on "multiplesortreceive", (event, ui) -> - items = _.sortBy ui.items, (item) -> - return $(item).index() - - index = _.min _.map items, (item) -> - return $(item).index() - - us = _.map items, (item) -> - item = $(item) - itemUs = item.scope().us + if !sameContainer + if dragMultipleItems.length + usList = _.map dragMultipleItems, (item) -> + return item = $(item).scope().us + else + usList = [$(item).scope().us] + if (dragMultipleItems.length) + _.each dragMultipleItems, (item) -> + deleteElement(item) + else deleteElement(item) + else + if dragMultipleItems.length + usList = _.map dragMultipleItems, (item) -> + return item = $(item).scope().us + else + usList = _.map items, (item) -> + item = $(item) + itemUs = item.scope().us - return itemUs + return itemUs - $scope.$emit("sprint:us:move", us, index, $scope.sprint.id) + $scope.$emit("sprint:us:move", usList, index, sprint) - $el.on "multiplesortstop", (event, ui) -> - # When parent not exists, do nothing - if ui.item.parent().length == 0 - return + scroll = autoScroll([window], { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) - itemUs = ui.item.scope().us - itemIndex = ui.item.index() - - # HACK: setTimeout prevents that firefox click - # event fires just after drag ends - setTimeout ( => - ui.item.find('a').removeClass('noclick') - ), 300 - - $scope.$emit("sprint:us:move", [itemUs], itemIndex, $scope.sprint.id) - - $el.on "sortstart", (event, ui) -> - ui.item.find('a').addClass('noclick') - - return {link:link} + $scope.$on "$destroy", -> + $el.off() + drake.destroy() + return {link: link} module.directive("tgBacklogSortable", [ "$tgRepo", @@ -228,17 +158,3 @@ module.directive("tgBacklogSortable", [ "$translate", BacklogSortableDirective ]) - -module.directive("tgBacklogEmptySortable", [ - "$tgRepo", - "$tgResources", - "$rootScope", - BacklogEmptySortableDirective -]) - -module.directive("tgSprintSortable", [ - "$tgRepo", - "$tgResources", - "$rootScope", - SprintSortableDirective -]) diff --git a/app/coffee/modules/common/tags.coffee b/app/coffee/modules/common/tags.coffee index 271d1e54..706fa4a4 100644 --- a/app/coffee/modules/common/tags.coffee +++ b/app/coffee/modules/common/tags.coffee @@ -108,6 +108,8 @@ LbTagLineDirective = ($rs, $template, $compile) -> templateTags = $template.get("common/tag/lb-tag-line-tags.html", true) + autocomplete = null + link = ($scope, $el, $attrs, $model) -> ## Render renderTags = (tags, tagsColors) -> @@ -130,7 +132,7 @@ LbTagLineDirective = ($rs, $template, $compile) -> resetInput = -> $el.find("input").val("") - $el.find("input").autocomplete("close") + autocomplete.close() ## Aux methods addValue = (value) -> @@ -190,22 +192,15 @@ LbTagLineDirective = ($rs, $template, $compile) -> deleteValue(value) bindOnce $scope, "project", (project) -> - positioningFunction = (position, elements) -> - menu = elements.element.element - menu.css("width", elements.target.width) - menu.css("top", position.top) - menu.css("left", position.left) + input = $el.find("input") - $el.find("input").autocomplete({ - source: _.keys(project.tags_colors) - position: { - my: "left top", - using: positioningFunction - } - select: (event, ui) -> - addValue(ui.item.value) - ui.item.value = "" - }) + autocomplete = new Awesomplete(input[0], { + list: _.keys(project.tags_colors) + }); + + input.on "awesomplete-selectcomplete", () -> + addValue(input.val()) + input.val("") $scope.$watch $attrs.ngModel, (tags) -> tagsColors = $scope.project?.tags_colors or [] @@ -235,6 +230,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi templateTags = $template.get("common/tag/tags-line-tags.html", true) link = ($scope, $el, $attrs, $model) -> + autocomplete = null + isEditable = -> if $attrs.requiredPerm? return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 @@ -268,7 +265,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi hideInput = -> $el.find("input").addClass("hidden").blur() resetInput = -> $el.find("input").val("") - $el.find("input").autocomplete("close") + + autocomplete.close() ## Aux methods addValue = $qqueue.bindAdd (value) -> @@ -366,22 +364,15 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi showAddTagButton() - positioningFunction = (position, elements) -> - menu = elements.element.element - menu.css("width", elements.target.width) - menu.css("top", position.top) - menu.css("left", position.left) + input = $el.find("input") - $el.find("input").autocomplete({ - source: _.keys(tags_colors) - position: { - my: "left top", - using: positioningFunction - } - select: (event, ui) -> - addValue(ui.item.value) - ui.item.value = "" - }) + autocomplete = new Awesomplete(input[0], { + list: _.keys(tags_colors) + }); + + input.on "awesomplete-selectcomplete", () -> + addValue(input.val()) + input.val("") $scope.$watch $attrs.ngModel, (model) -> return if not model diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index 6945915e..c23f4f28 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -418,8 +418,6 @@ module.directive("tgKanbanArchivedStatusIntro", ["$translate", KanbanArchivedSta KanbanUserstoryDirective = ($rootscope, $loading, $rs, $rs2) -> link = ($scope, $el, $attrs, $model) -> - $el.disableSelection() - $scope.$watch "us", (us) -> if us.is_blocked and not $el.hasClass("blocked") $el.addClass("blocked") @@ -498,8 +496,6 @@ module.directive("tgKanbanSquishColumn", ["$tgResources", KanbanSquishColumnDire KanbanWipLimitDirective = -> link = ($scope, $el, $attrs) -> - $el.disableSelection() - status = $scope.$eval($attrs.tgKanbanWipLimit) redrawWipLimit = => diff --git a/app/coffee/modules/kanban/sortable.coffee b/app/coffee/modules/kanban/sortable.coffee index 9051b77e..41cbb23d 100644 --- a/app/coffee/modules/kanban/sortable.coffee +++ b/app/coffee/modules/kanban/sortable.coffee @@ -55,16 +55,23 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) -> itemEl.off() itemEl.remove() - tdom.sortable({ - handle: ".kanban-task-inner" - dropOnEmpty: true - connectWith: ".kanban-uses-box" - revert: 400 + containers = _.map $el.find('.task-column'), (item) -> + return item + + drake = dragula(containers, { + copySortSource: false, + copy: false, + mirrorContainer: tdom[0], + moves: (item) -> + return $(item).hasClass('kanban-task') }) - tdom.on "sortstop", (event, ui) -> - parentEl = ui.item.parent() - itemEl = ui.item + drake.on 'drag', (item) -> + oldParentScope = $(item).parent().scope() + + drake.on 'dragend', (item) -> + parentEl = $(item).parent() + itemEl = $(item) itemUs = itemEl.scope().us itemIndex = itemEl.index() newParentScope = parentEl.scope() @@ -78,14 +85,17 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) -> $scope.$apply -> $rootscope.$broadcast("kanban:us:move", itemUs, itemUs.status, newStatusId, itemIndex) - ui.item.find('a').removeClass('noclick') + scroll = autoScroll(containers, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) - tdom.on "sortstart", (event, ui) -> - oldParentScope = ui.item.parent().scope() - ui.item.find('a').addClass('noclick') - - $scope.$on "$destroy", -> - $el.off() + $scope.$on "$destroy", -> + $el.off() + drake.destroy() return {link: link} diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 11dc0486..d32b159f 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -309,8 +309,6 @@ module.directive("tgTaskboard", ["$rootScope", TaskboardDirective]) TaskboardTaskDirective = ($rootscope, $loading, $rs, $rs2) -> link = ($scope, $el, $attrs, $model) -> - $el.disableSelection() - $scope.$watch "task", (task) -> if task.is_blocked and not $el.hasClass("blocked") $el.addClass("blocked") diff --git a/app/coffee/modules/taskboard/sortable.coffee b/app/coffee/modules/taskboard/sortable.coffee index 19775335..c29875ef 100644 --- a/app/coffee/modules/taskboard/sortable.coffee +++ b/app/coffee/modules/taskboard/sortable.coffee @@ -39,9 +39,9 @@ module = angular.module("taigaBacklog") TaskboardSortableDirective = ($repo, $rs, $rootscope) -> link = ($scope, $el, $attrs) -> - bindOnce $scope, "project", (project) -> + bindOnce $scope, "tasks", (xx) -> # If the user has not enough permissions we don't enable the sortable - if not (project.my_permissions.indexOf("modify_us") > -1) + if not ($scope.project.my_permissions.indexOf("modify_us") > -1) return oldParentScope = null @@ -55,16 +55,22 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> itemEl.off() itemEl.remove() - tdom.sortable({ - handle: ".taskboard-task-inner", - dropOnEmpty: true - connectWith: ".taskboard-tasks-box" - revert: 400 + containers = _.map $el.find('.task-column'), (item) -> + return item + + drake = dragula(containers, { + copySortSource: false, + copy: false, + mirrorContainer: $el[0], + moves: (item) -> return $(item).hasClass('taskboard-task') }) - tdom.on "sortstop", (event, ui) -> - parentEl = ui.item.parent() - itemEl = ui.item + drake.on 'drag', (item) -> + oldParentScope = $(item).parent().scope() + + drake.on 'dragend', (item) -> + parentEl = $(item).parent() + itemEl = $(item) itemTask = itemEl.scope().task itemIndex = itemEl.index() newParentScope = parentEl.scope() @@ -80,14 +86,17 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> $scope.$apply -> $rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex) - ui.item.find('a').removeClass('noclick') + scroll = autoScroll(containers, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) - tdom.on "sortstart", (event, ui) -> - oldParentScope = ui.item.parent().scope() - ui.item.find('a').addClass('noclick') - - $scope.$on "$destroy", -> - $el.off() + $scope.$on "$destroy", -> + $el.off() + drake.destroy() return {link: link} diff --git a/app/js/dom-autoscroller.js b/app/js/dom-autoscroller.js new file mode 100644 index 00000000..08831141 --- /dev/null +++ b/app/js/dom-autoscroller.js @@ -0,0 +1,573 @@ +// The MIT License (MIT) + +// Copyright (c) 2016 Quentin Engles + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +(function() { + // more_events + + function MoreEvents(context){ + this.listeners = {}; + this.__context = context || this; + } + + MoreEvents.prototype = { + constructor: MoreEvents, + on: function(event, listener){ + this.listeners[event] = this.listeners[event] || []; + this.listeners[event].push(listener); + return this; + }, + one: function(event, listener){ + function onceListener(){ + listener.apply(this, arguments); + this.off(event, onceListener); + return this; + } + return this.on(event, onceListener); + }, + emit: function(event){ + if(typeof this.listeners[event] === 'undefined' || !this.listeners[event].length) + return this; + + var args = Array.prototype.slice.call(arguments, 1), + canRun = this.listeners[event].length; + + do{ + this.listeners[event][--canRun].apply(this.__context, args); + }while(canRun); + + return this; + }, + off: function(event, listener){ + if(this.listeners[event] === undefined || !this.listeners[event].length) + return this; + this.listeners[event] = this.listeners[event].filter(function(item){ + return item !== listener; + }); + return this; + }, + dispose: function(){ + for(var n in this){ + this[n] = null; + } + } + }; + + // pointer_point + + var Emitter = MoreEvents; + + if(!Date.now){ Date.now = function(){ return new Date().getTime() } } + + function LocalDimensions(point, rect){ + for(var n in rect) + setProp(this, n, rect[n]); + + setProp(this, 'x', point.x - rect.left+1); + setProp(this, 'y', point.y - rect.top+1); + + setProp(this, 'north', (((rect.bottom - rect.top) / 2)-this.y)); + setProp(this, 'south', ((-(rect.bottom - rect.top) / 2)+this.y)); + setProp(this, 'east', (((rect.right - rect.left) / 2)-this.x)); + setProp(this, 'west', ((-(rect.right - rect.left) / 2)+this.x)); + + + function setProp(self, name, value){ + Object.defineProperty(self, name, { + value: value, + configurable: true, + writable: false + }); + } + } + function Point(elements){ + var self = this, el = []; + + if(typeof elements.length === 'undefined'){ + elements = [elements]; + } + + for(var i=0; i (startY - buf) && self.y < (startY + buf) && + self.x > (startX - buf) && self.x < (startX + buf))){ + //If there is scrolling there was a touch flick. + if(!scrolling){ + //No touch flick so + self.previous = null; + self.origin = null; + e.preventDefault(); + return false; + + } + } + } + + scrolling = false; + self.previous = null; + self.origin = null; + } + + function toPoint(event){ + var dot, eventDoc, doc, body, pageX, pageY; + var target, newTarget = null, leaving = null; + + event = event || window.event; // IE-ism + target = event.target || event.srcElement; + + //Supporting touch + //http://www.creativebloq.com/javascript/make-your-site-work-touch-devices-51411644 + if(event.targetTouches) { + event.pageX = event.targetTouches[0].clientX; + event.pageY = event.targetTouches[0].clientY; + event.clientX = event.targetTouches[0].clientX; + event.clientY = event.targetTouches[0].clientY; + }else + + // If pageX/Y aren't available and clientX/Y are, + // calculate pageX/Y - logic taken from jQuery. + // (This is to support old IE) + if (event.pageX === null && event.clientX !== null) { + eventDoc = (event.target && event.target.ownerDocument) || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = event.clientX + + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - + (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + + (doc && doc.scrollTop || body && body.scrollTop || 0) - + (doc && doc.clientTop || body && body.clientTop || 0 ); + } + + if(self.x && self.y){ + if(event.pageX < self.x) + direction.h = 'left'; + else if(event.pageX > self.x) + direction.h = 'right'; + if(event.pageY < self.y) + direction.v = 'up'; + else if(event.pageY > self.y) + direction.v = 'down'; + + lastmousex = self.x; + lastmousey = self.y; + } + + pos = {}; + //Prefer the viewport with clientX, and clientY. + //pageX, and pageY change too often. + pos.x = event.clientX;//event.pageX; + pos.y = event.clientY;//event.pageY; + + if(self.current === null || self.outside(self.current)){ + for(var i=0; i downTime + (special.hold[i].data || 2000)){ + special.hold[i].callback.call(this, el, rect); + } + } + } + downTime = 0; + }); + + function removeSpecial(event, cb){ + for(var i=0; i rect.top && this.y < rect.bottom && + this.x > rect.left && this.x < rect.right); + }, + outside: function(el){ + if(!el) throw new TypeError('Cannot be outside '+el); + return !this.inside(el); + } + }; + + function elementFromPoint(x, y){ + if(document.getElementFromPoint) + return document.getElementFromPoint(x, y); + else + return document.elementFromPoint(x, y); + return null; + } + + function safeObject(src){ + var obj = {}; + for(var n in src) + obj[n] = src[n]; + return obj; + } + + function getRect(el){ + if(el === window){ + return { + top: 0, + left: 0, + right: window.innerWidth, + bottom: window.innerHeight, + width: window.innerWidth, + height: window.innerHeight + }; + + }else{ + return el.getBoundingClientRect(); + } + } + + var pointer = function(element){ + return new Point(element); + }; + + + + // Autscroller + + function AutoScrollerFactory(element, options){ + return new AutoScroller(element, options); + } + + function AutoScroller(elements, options){ + var self = this, pixels = 2; + options = options || {}; + + this.margin = options.margin || -1; + this.scrolling = false; + this.scrollWhenOutside = options.scrollWhenOutside || false; + + this.point = pointer(elements); + + if(!isNaN(options.pixels)){ + pixels = options.pixels; + } + + if(typeof options.autoScroll === 'boolean'){ + this.autoScroll = options.autoScroll ? function(){return true;} : function(){return false;}; + }else if(typeof options.autoScroll === 'undefined'){ + this.autoScroll = function(){return false;}; + }else if(typeof options.autoScroll === 'function'){ + this.autoScroll = options.autoScroll; + } + + this.destroy = function() { + this.point.destroy(); + }; + + Object.defineProperties(this, { + down: { + get: function(){ return self.point.down; } + }, + interval: { + get: function(){ return 1/pixels * 1000; } + }, + pixels: { + set: function(i){ pixels = i; }, + get: function(){ return pixels; } + } + }); + + this.point.on('move', function(el, rect){ + + if(!el) return; + if(!self.autoScroll()) return; + if(!self.scrollWhenOutside && this.outside(el)) return; + + if(self.point.y < rect.top + self.margin){ + autoScrollV(el, -1, rect); + }else if(self.point.y > rect.bottom - self.margin){ + autoScrollV(el, 1, rect); + } + + if(self.point.x < rect.left + self.margin){ + autoScrollH(el, -1, rect); + }else if(self.point.x > rect.right - self.margin){ + autoScrollH(el, 1, rect); + } + }); + + function autoScrollV(el, amount, rect){ + //if(!self.down) return; + if(!self.autoScroll()) return; + if(!self.scrollWhenOutside && self.point.outside(el)) return; + if(el === window){ + window.scrollTo(el.pageXOffset, el.pageYOffset + amount); + }else{ + el.scrollTop = el.scrollTop + amount; + } + + setTimeout(function(){ + if(self.point.y < rect.top + self.margin){ + autoScrollV(el, amount, rect); + }else if(self.point.y > rect.bottom - self.margin){ + autoScrollV(el, amount, rect); + } + }, self.interval); + } + + function autoScrollH(el, amount, rect){ + //if(!self.down) return; + if(!self.autoScroll()) return; + if(!self.scrollWhenOutside && self.point.outside(el)) return; + if(el === window){ + window.scrollTo(el.pageXOffset + amount, el.pageYOffset); + }else{ + el.scrollLeft = el.scrollLeft + amount; + } + + setTimeout(function(){ + if(self.point.x < rect.left + self.margin){ + autoScrollH(el, amount, rect); + }else if(self.point.x > rect.right - self.margin){ + autoScrollH(el, amount, rect); + } + }, self.interval); + } + + } + + window.autoScroll = AutoScrollerFactory; +}()); diff --git a/app/js/dragula-drag-multiple.js b/app/js/dragula-drag-multiple.js new file mode 100644 index 00000000..1f7c521a --- /dev/null +++ b/app/js/dragula-drag-multiple.js @@ -0,0 +1,221 @@ +(function() { + var multipleSortableClass = 'ui-multisortable-multiple'; + var mainClass = 'main-drag-item'; + var inProgress = false; + + var reset = function(elm) { + $(elm) + .removeAttr('style') + .removeClass('tg-backlog-us-mirror') + .removeClass('backlog-us-mirror') + .data('dragMultipleIndex', null) + .data('dragMultipleActive', false); + }; + + var sort = function(positions) { + var current = dragMultiple.items.elm; + + positions.after.reverse(); + + $.each(positions.after, function () { + reset(this); + current.after(this); + }); + + $.each(positions.before, function () { + reset(this); + current.before(this); + }); + }; + + var drag = function() { + var current = dragMultiple.items.elm; + var container = dragMultiple.items.container; + + var shadow = dragMultiple.items.shadow; + + // following the drag element + var currentLeft = shadow.position().left; + var currentTop = shadow.position().top; + var height = shadow.outerHeight(); + + _.forEach(dragMultiple.items.draggingItems, function(elm, index) { + var elmIndex = parseInt(elm.data('dragMultipleIndex'), 10); + var top = currentTop + (elmIndex * height); + + elm + .css({ + top: top, + left: currentLeft + }); + }); + }; + + var stop = function() { + inProgress = false; + + refreshOriginal(); + + var current = dragMultiple.items.elm; + var container = dragMultiple.items.container; + + $(window).off('mousemove.dragmultiple'); + + // reset + dragMultiple.items = {}; + + $('.' + mainClass).removeClass(mainClass); + $('.tg-backlog-us-mirror').remove(); + $('.backlog-us-mirror').removeClass('backlog-us-mirror'); + + $('.tg-backlog-us-dragging') + .removeClass('tg-backlog-us-dragging') + .show(); + + return $('.' + multipleSortableClass); + }; + + var refreshOriginal = function() { + var index = parseInt(dragMultiple.items.elm.data('dragMultipleIndex'), 10); + + var after = []; + var before = []; + + _.forEach(dragMultiple.items.draggedItemsOriginal, function(item) { + if (parseInt($(item).data('dragMultipleIndex'), 10) > index) { + after.push(item); + } else { + before.push(item); + } + }); + + after.reverse(); + + _.forEach(after, function(item) { + $(item).insertAfter(dragMultiple.items.elm); + }); + + _.forEach(before, function(item) { + $(item).insertBefore(dragMultiple.items.elm); + }); + }; + + + var isMultiple = function(elm, container) { + var items = $(container).find('.' + multipleSortableClass); + + if (!$(elm).hasClass(multipleSortableClass) || !(items.length > 1)) { + return false; + } + + return true; + }; + + var setIndex = function(items) { + var before = []; + var after = []; + var mainFound = false; + _.forEach(items, function(item, index) { + if ($(item).data('dragMultipleIndex') === 0) { + mainFound = true; + return; + } + + if (mainFound) { + after.push(item); + } else { + before.push(item); + } + }); + + before.reverse(); + + _.forEach(after, function(item, index) { + $(item).data('dragMultipleIndex', index + 1); + }); + + _.forEach(before, function(item, index) { + $(item).data('dragMultipleIndex', -index - 1); + }); + }; + + var dragMultiple = {}; + + dragMultiple.prepare = function(elm, container) { + inProgress = true; + + var items = $(container).find('.' + multipleSortableClass); + + $(elm) + .data('dragmultiple:originalPosition', $(elm).position()) + .data('dragMultipleActive', true); + + dragMultiple.items = {}; + + dragMultiple.items.elm = $(elm); + dragMultiple.items.container = $(container); + + dragMultiple.items.elm.data('dragMultipleIndex', 0); + + setIndex(items); + + dragMultiple.items.shadow = $('.gu-mirror'); + + dragMultiple.items.elm.addClass(mainClass); + + items = _.filter(items, function(item) { + return !$(item).hasClass(mainClass); + }); + + dragMultiple.items.draggedItemsOriginal = items; + + var itemsCloned = _.map(items, function (item) { + clone = $(item).clone(true); + + clone + .addClass('backlog-us-mirror') + .addClass('tg-backlog-us-mirror') + .data('dragmultiple:originalPosition', $(item).position()) + .data('dragMultipleActive', true) + .css({ + zIndex: '9999', + opacity: '0.8', + position: 'fixed', + width: dragMultiple.items.elm.outerWidth(), + height: dragMultiple.items.elm.outerHeight() + }); + + $(item) + .hide() + .addClass('tg-backlog-us-dragging'); + + return clone; + }); + + dragMultiple.items.draggingItems = itemsCloned; + + $(document.body).append(itemsCloned); + }; + + dragMultiple.start = function(item, container) { + if (isMultiple(item, container)) { + $(window).on('mousemove.dragmultiple', function() { + if (!inProgress) { + dragMultiple.prepare(item, container); + } + + drag(); + }); + } + }; + + dragMultiple.stop = function() { + if (inProgress) { + return stop(); + } else { + return []; + } + }; + + window.dragMultiple = dragMultiple; +}()); diff --git a/app/js/jquery-ui.drag-multiple-custom.js b/app/js/jquery-ui.drag-multiple-custom.js deleted file mode 100644 index 15f449ff..00000000 --- a/app/js/jquery-ui.drag-multiple-custom.js +++ /dev/null @@ -1,161 +0,0 @@ -(function($) { - var multipleSortableClass = 'ui-multisortable-multiple'; - var dragStarted = false; - - var multiSort = {}; - - multiSort.isBelow = function(elm, compare) { - var elmOriginalPosition = elm.data('dragmultiple:originalPosition'); - var compareOriginalPosition = compare.data('dragmultiple:originalPosition'); - - return elmOriginalPosition.top > compareOriginalPosition.top; - }; - - multiSort.reset = function(elm) { - $(elm) - .removeClass("ui-sortable-helper") - .removeAttr('style') - .data('dragMultipleActive', false); - }; - - multiSort.sort = function(current, positions) { - positions.after.reverse(); - - $.each(positions.after, function () { - multiSort.reset(this); - current.after(this); - }); - - $.each(positions.before, function () { - multiSort.reset(this); - current.before(this); - }); - }; - - multiSort.sortPositions = function(elm, current) { - //saved if the elements are after or before the current - var insertAfter = []; - var insertBefore = []; - - $(elm).find('.' + multipleSortableClass).each(function () { - var elm = $(this); - - if (elm[0] === current[0] || !current.hasClass(multipleSortableClass)) return; - - if (multiSort.isBelow(elm, current)) { - insertAfter.push(elm); - } else { - insertBefore.push(elm); - } - }); - - return {'after': insertAfter, 'before': insertBefore}; - }; - - $.widget( "ui.sortable", $.ui.sortable, { - _mouseStart: function() { - dragStarted = false; - - this._superApply( arguments ); - }, - _createHelper: function () { - var helper = this._superApply( arguments ); - - if ($(helper).hasClass(multipleSortableClass)) { - $(this.element).find('.' + multipleSortableClass).each(function () { - $(this) - .data('dragmultiple:originalPosition', $(this).position()) - .data('dragMultipleActive', true); - }); - } - - return helper; - }, - _mouseStop: function (event, ui) { - var current = this.helper; - var elms = []; - - if (current.hasClass(multipleSortableClass)) { - elms = $(this.element).find('.' + multipleSortableClass); - } - - if (!elms.length) { - elms = [current]; - } - - //save the order of the elements relative to the main - var positions = multiSort.sortPositions(this.element, current); - - this._superApply( arguments ); - - if (this.element !== this.currentContainer.element) { - // change to another sortable list - multiSort.sort(current, positions); - - $(this.currentContainer.element).trigger('multiplesortreceive', { - 'item': current, - 'items': elms, - 'source': this.element - }); - } else if (current.hasClass(multipleSortableClass)) { - // sort in the same list - multiSort.sort(current, positions); - } - - $(this.element).trigger('multiplesortstop', { - 'item': current, - 'items': elms - }); - }, - _mouseDrag: function(key, value) { - this._super(key, value); - - var current = this.helper; - - if (!current.hasClass(multipleSortableClass)) return; - - // following the drag element - var currentLeft = current.position().left; - var currentTop = current.position().top; - var currentZIndex = current.css('z-index'); - var currentPosition = current.css('position'); - - var positions = multiSort.sortPositions(this.element, current); - - positions.before.reverse(); - - [{'positions': positions.after, type: 'after'}, - {'positions': positions.before, type: 'before'}] - .forEach(function (item) { - $.each(item.positions, function (index, elm) { - var top; - - if (item.type === 'after') { - top = currentTop + ((index + 1) * current.outerHeight()); - } else { - top = currentTop - ((index + 1) * current.outerHeight()); - } - - elm - .addClass("ui-sortable-helper") - .css({ - width: elm.outerWidth(), - height: elm.outerHeight(), - position: currentPosition, - zIndex: currentZIndex, - top: top, - left: currentLeft - }); - }); - }); - - // it only refresh position the first time because - // jquery-ui has saved the old positions of the draggable elements - // and with this will remove all elements with dragMultipleActive - if (!dragStarted) { - dragStarted = true; - this.refreshPositions(); - } - } - }); -}(jQuery)) diff --git a/app/js/jquery.ui.git-custom.js b/app/js/jquery.ui.git-custom.js deleted file mode 100644 index 855907fe..00000000 --- a/app/js/jquery.ui.git-custom.js +++ /dev/null @@ -1,7033 +0,0 @@ -/*! jQuery UI - v1.11.4 - 2015-05-19 -* http://jqueryui.com -* Includes: core.js, widget.js, mouse.js, position.js, draggable.js, droppable.js, resizable.js, selectable.js, sortable.js, autocomplete.js, menu.js -* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ - -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - - // AMD. Register as an anonymous module. - define([ "jquery" ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -}(function( $ ) { -/*! - * 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/ - */ - - -// $.ui might exist from components with no dependencies, e.g., $.ui.position -$.ui = $.ui || {}; - -$.extend( $.ui, { - version: "1.11.4", - - keyCode: { - BACKSPACE: 8, - COMMA: 188, - DELETE: 46, - DOWN: 40, - END: 35, - ENTER: 13, - ESCAPE: 27, - HOME: 36, - LEFT: 37, - PAGE_DOWN: 34, - PAGE_UP: 33, - PERIOD: 190, - RIGHT: 39, - SPACE: 32, - TAB: 9, - UP: 38 - } -}); - -// plugins -$.fn.extend({ - 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; - } - return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); - }).eq( 0 ); - - return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; - }, - - 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 ( /^ui-id-\d+$/.test( this.id ) ) { - $( this ).removeAttr( "id" ); - } - }); - } -}); - -// selectors -function focusable( element, isTabIndexNotNaN ) { - var map, mapName, img, - nodeName = element.nodeName.toLowerCase(); - if ( "area" === nodeName ) { - map = element.parentNode; - mapName = map.name; - if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { - return false; - } - img = $( "img[usemap='#" + mapName + "']" )[ 0 ]; - return !!img && visible( img ); - } - return ( /^(input|select|textarea|button|object)$/.test( nodeName ) ? - !element.disabled : - "a" === nodeName ? - element.href || isTabIndexNotNaN : - isTabIndexNotNaN) && - // the element and all of its ancestors must be visible - visible( element ); -} - -function visible( element ) { - return $.expr.filters.visible( element ) && - !$( element ).parents().addBack().filter(function() { - return $.css( this, "visibility" ) === "hidden"; - }).length; -} - -$.extend( $.expr[ ":" ], { - data: $.expr.createPseudo ? - $.expr.createPseudo(function( dataName ) { - return function( elem ) { - return !!$.data( elem, dataName ); - }; - }) : - // support: jQuery <1.8 - function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - }, - - focusable: function( element ) { - return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); - }, - - tabbable: function( element ) { - var tabIndex = $.attr( element, "tabindex" ), - isTabIndexNaN = isNaN( tabIndex ); - return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); - } -}); - -// support: jQuery <1.8 -if ( !$( "" ).outerWidth( 1 ).jquery ) { - $.each( [ "Width", "Height" ], function( i, name ) { - var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], - type = name.toLowerCase(), - orig = { - innerWidth: $.fn.innerWidth, - innerHeight: $.fn.innerHeight, - outerWidth: $.fn.outerWidth, - outerHeight: $.fn.outerHeight - }; - - function reduce( elem, size, border, margin ) { - $.each( side, function() { - size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; - if ( border ) { - size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; - } - if ( margin ) { - size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; - } - }); - return size; - } - - $.fn[ "inner" + name ] = function( size ) { - if ( size === undefined ) { - return orig[ "inner" + name ].call( this ); - } - - return this.each(function() { - $( this ).css( type, reduce( this, size ) + "px" ); - }); - }; - - $.fn[ "outer" + name] = function( size, margin ) { - if ( typeof size !== "number" ) { - return orig[ "outer" + name ].call( this, size ); - } - - return this.each(function() { - $( this).css( type, reduce( this, size, true, margin ) + "px" ); - }); - }; - }); -} - -// support: jQuery <1.8 -if ( !$.fn.addBack ) { - $.fn.addBack = function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - }; -} - -// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) -if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { - $.fn.removeData = (function( removeData ) { - return function( key ) { - if ( arguments.length ) { - return removeData.call( this, $.camelCase( key ) ); - } else { - return removeData.call( this ); - } - }; - })( $.fn.removeData ); -} - -// deprecated -$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); - -$.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 ), - - 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" ); - }, - - 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; - } - } - elem = elem.parent(); - } - } - - return 0; - } -}); - -// $.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 ]; - - 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 ); - } - } - } -}; - - -/*! - * 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 - // so that it can be used as a mixin for multiple widgets (#8876) - proxiedPrototype = {}, - namespace = name.split( "." )[ 0 ]; - - name = name.split( "." )[ 1 ]; - fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - // create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { - return !!$.data( elem, fullName ); - }; - - $[ namespace ] = $[ namespace ] || {}; - existingConstructor = $[ namespace ][ name ]; - constructor = $[ namespace ][ name ] = function( options, element ) { - // allow instantiation without "new" keyword - if ( !this._createWidget ) { - return new constructor( options, element ); - } - - // allow instantiation without initializing for simple inheritance - // must use "new" keyword (the code above always passes args) - if ( arguments.length ) { - this._createWidget( options, element ); - } - }; - // extend with the existing constructor to carry over any static properties - $.extend( constructor, existingConstructor, { - version: prototype.version, - // copy the object used to create the prototype in case we need to - // redefine the widget later - _proto: $.extend( {}, prototype ), - // track widgets that inherit from this widget in case this widget is - // redefined after a widget inherits from it - _childConstructors: [] - }); - - basePrototype = new base(); - // we need to make the options hash a property directly on the new instance - // otherwise we'll modify the options hash on the prototype that we're - // inheriting from - basePrototype.options = $.widget.extend( {}, basePrototype.options ); - $.each( prototype, function( prop, value ) { - if ( !$.isFunction( value ) ) { - proxiedPrototype[ prop ] = value; - return; - } - proxiedPrototype[ prop ] = (function() { - var _super = function() { - return base.prototype[ prop ].apply( this, arguments ); - }, - _superApply = function( args ) { - return base.prototype[ prop ].apply( this, args ); - }; - return function() { - var __super = this._super, - __superApply = this._superApply, - returnValue; - - this._super = _super; - this._superApply = _superApply; - - returnValue = value.apply( this, arguments ); - - this._super = __super; - this._superApply = __superApply; - - return returnValue; - }; - })(); - }); - constructor.prototype = $.widget.extend( basePrototype, { - // 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) : name - }, proxiedPrototype, { - constructor: constructor, - namespace: namespace, - widgetName: name, - widgetFullName: fullName - }); - - // If this widget is being redefined then we need to find all widgets that - // are inheriting from it and redefine all of them so that they inherit from - // the new version of this widget. We're essentially trying to replace one - // level in the prototype chain. - if ( existingConstructor ) { - $.each( existingConstructor._childConstructors, function( i, child ) { - var childPrototype = child.prototype; - - // redefine the child widget using the same prototype that was - // originally used, but inherit from the new version of the base - $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); - }); - // remove the list of existing child constructors from the old constructor - // so the old child constructors can be garbage collected - delete existingConstructor._childConstructors; - } else { - base._childConstructors.push( constructor ); - } - - $.widget.bridge( name, constructor ); - - return constructor; -}; - -$.widget.extend = function( target ) { - var input = widget_slice.call( arguments, 1 ), - inputIndex = 0, - inputLength = input.length, - key, - value; - for ( ; inputIndex < inputLength; inputIndex++ ) { - for ( key in input[ inputIndex ] ) { - value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { - // Clone objects - if ( $.isPlainObject( value ) ) { - target[ key ] = $.isPlainObject( target[ key ] ) ? - $.widget.extend( {}, target[ key ], value ) : - // Don't extend strings, arrays, etc. with objects - $.widget.extend( {}, value ); - // Copy everything else by reference - } else { - target[ key ] = value; - } - } - } - } - return target; -}; - -$.widget.bridge = function( name, object ) { - var fullName = object.prototype.widgetFullName || name; - $.fn[ name ] = function( options ) { - var isMethodCall = typeof options === "string", - args = widget_slice.call( arguments, 1 ), - returnValue = this; - - 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 + "'" ); - } - if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { - return $.error( "no such method '" + options + "' for " + name + " widget instance" ); - } - methodValue = instance[ options ].apply( instance, args ); - if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue && methodValue.jquery ? - returnValue.pushStack( methodValue.get() ) : - methodValue; - return false; - } - }); - } 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 || {} ); - if ( instance._init ) { - instance._init(); - } - } else { - $.data( this, fullName, new object( options, this ) ); - } - }); - } - - return returnValue; - }; -}; - -$.Widget = function( /* options, element */ ) {}; -$.Widget._childConstructors = []; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - defaultElement: "
", - options: { - disabled: false, - - // callbacks - create: null - }, - _createWidget: function( options, element ) { - element = $( element || this.defaultElement || this )[ 0 ]; - this.element = $( element ); - this.uuid = widget_uuid++; - this.eventNamespace = "." + this.widgetName + this.uuid; - - this.bindings = $(); - this.hoverable = $(); - this.focusable = $(); - - if ( element !== this ) { - $.data( element, this.widgetFullName, this ); - this._on( true, this.element, { - remove: function( event ) { - if ( event.target === element ) { - this.destroy(); - } - } - }); - this.document = $( element.style ? - // element within the document - element.ownerDocument : - // element is window or document - element.document || element ); - 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(); - }, - _getCreateOptions: $.noop, - _getCreateEventData: $.noop, - _create: $.noop, - _init: $.noop, - - destroy: function() { - this._destroy(); - // we can probably remove the unbind calls in 2.0 - // all event bindings should go through this._on() - this.element - .unbind( this.eventNamespace ) - .removeData( this.widgetFullName ) - // support: jquery <1.6.3 - // http://bugs.jquery.com/ticket/9413 - .removeData( $.camelCase( this.widgetFullName ) ); - this.widget() - .unbind( this.eventNamespace ) - .removeAttr( "aria-disabled" ) - .removeClass( - this.widgetFullName + "-disabled " + - "ui-state-disabled" ); - - // clean up events and states - this.bindings.unbind( this.eventNamespace ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); - }, - _destroy: $.noop, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key, - parts, - curOption, - i; - - if ( arguments.length === 0 ) { - // don't return a reference to the internal hash - return $.widget.extend( {}, this.options ); - } - - if ( typeof key === "string" ) { - // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } - options = {}; - parts = key.split( "." ); - key = parts.shift(); - if ( parts.length ) { - curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); - for ( i = 0; i < parts.length - 1; i++ ) { - curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; - curOption = curOption[ parts[ i ] ]; - } - key = parts.pop(); - if ( arguments.length === 1 ) { - return curOption[ key ] === undefined ? null : curOption[ key ]; - } - curOption[ key ] = value; - } else { - if ( arguments.length === 1 ) { - return this.options[ key ] === undefined ? null : this.options[ key ]; - } - options[ key ] = value; - } - } - - this._setOptions( options ); - - return this; - }, - _setOptions: function( options ) { - var key; - - for ( key in options ) { - this._setOption( key, options[ key ] ); - } - - return this; - }, - _setOption: function( key, value ) { - this.options[ key ] = value; - - if ( key === "disabled" ) { - this.widget() - .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._setOptions({ disabled: false }); - }, - disable: function() { - return this._setOptions({ disabled: true }); - }, - - _on: function( suppressDisabledCheck, element, handlers ) { - var delegateElement, - instance = this; - - // no suppressDisabledCheck flag, shuffle arguments - if ( typeof suppressDisabledCheck !== "boolean" ) { - handlers = element; - element = suppressDisabledCheck; - suppressDisabledCheck = false; - } - - // no element argument, shuffle and use this.element - if ( !handlers ) { - handlers = element; - element = this.element; - delegateElement = this.widget(); - } else { - element = delegateElement = $( element ); - this.bindings = this.bindings.add( element ); - } - - $.each( handlers, function( event, handler ) { - function handlerProxy() { - // allow widgets to customize the disabled handling - // - disabled as an array instead of boolean - // - disabled class as method for disabling individual parts - if ( !suppressDisabledCheck && - ( instance.options.disabled === true || - $( this ).hasClass( "ui-state-disabled" ) ) ) { - return; - } - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - - // copy the guid so direct unbinding works - if ( typeof handler !== "string" ) { - handlerProxy.guid = handler.guid = - handler.guid || handlerProxy.guid || $.guid++; - } - - var match = event.match( /^([\w:-]*)\s*(.*)$/ ), - eventName = match[1] + instance.eventNamespace, - selector = match[2]; - if ( selector ) { - delegateElement.delegate( selector, eventName, handlerProxy ); - } else { - element.bind( eventName, handlerProxy ); - } - }); - }, - - _off: function( element, eventName ) { - 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 ) { - function handlerProxy() { - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - var instance = this; - return setTimeout( handlerProxy, delay || 0 ); - }, - - _hoverable: function( element ) { - this.hoverable = this.hoverable.add( element ); - this._on( element, { - mouseenter: function( event ) { - $( event.currentTarget ).addClass( "ui-state-hover" ); - }, - mouseleave: function( event ) { - $( event.currentTarget ).removeClass( "ui-state-hover" ); - } - }); - }, - - _focusable: function( element ) { - this.focusable = this.focusable.add( element ); - this._on( element, { - focusin: function( event ) { - $( event.currentTarget ).addClass( "ui-state-focus" ); - }, - focusout: function( event ) { - $( event.currentTarget ).removeClass( "ui-state-focus" ); - } - }); - }, - - _trigger: function( type, event, data ) { - var prop, orig, - callback = this.options[ type ]; - - data = data || {}; - event = $.Event( event ); - event.type = ( type === this.widgetEventPrefix ? - type : - this.widgetEventPrefix + type ).toLowerCase(); - // the original event may come from any element - // so we need to reset the target on the new event - event.target = this.element[ 0 ]; - - // copy original event properties over to the new event - orig = event.originalEvent; - if ( orig ) { - for ( prop in orig ) { - if ( !( prop in event ) ) { - event[ prop ] = orig[ prop ]; - } - } - } - - this.element.trigger( event, data ); - return !( $.isFunction( callback ) && - callback.apply( this.element[0], [ event ].concat( data ) ) === false || - event.isDefaultPrevented() ); - } -}; - -$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { - $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { - if ( typeof options === "string" ) { - options = { effect: options }; - } - var hasOptions, - effectName = !options ? - method : - options === true || typeof options === "number" ? - defaultEffect : - options.effect || defaultEffect; - options = options || {}; - if ( typeof options === "number" ) { - options = { duration: options }; - } - hasOptions = !$.isEmptyObject( options ); - options.complete = callback; - if ( options.delay ) { - element.delay( options.delay ); - } - if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { - element[ method ]( options ); - } else if ( effectName !== method && element[ effectName ] ) { - element[ effectName ]( options.duration, options.easing, callback ); - } else { - element.queue(function( next ) { - $( this )[ method ](); - if ( callback ) { - callback.call( element[ 0 ] ); - } - next(); - }); - } - }; -}); - -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; -}); - -var mouse = $.widget("ui.mouse", { - version: "1.11.4", - options: { - cancel: "input,textarea,button,select,option", - distance: 1, - delay: 0 - }, - _mouseInit: function() { - var that = this; - - this.element - .bind("mousedown." + this.widgetName, function(event) { - return that._mouseDown(event); - }) - .bind("click." + this.widgetName, function(event) { - if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { - $.removeData(event.target, that.widgetName + ".preventClickEvent"); - event.stopImmediatePropagation(); - return false; - } - }); - - this.started = false; - }, - - // TODO: make sure destroying one instance of mouse doesn't mess with - // other instances of mouse - _mouseDestroy: function() { - this.element.unbind("." + this.widgetName); - if ( this._mouseMoveDelegate ) { - 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; - } - - this._mouseMoved = false; - - // we may have missed mouseup (out of window) - (this._mouseStarted && this._mouseUp(event)); - - this._mouseDownEvent = event; - - var that = this, - btnIsLeft = (event.which === 1), - // event.target.nodeName works around a bug in IE 8 with - // disabled inputs (#7620) - elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); - if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { - return true; - } - - this.mouseDelayMet = !this.options.delay; - if (!this.mouseDelayMet) { - this._mouseDelayTimer = setTimeout(function() { - that.mouseDelayMet = true; - }, this.options.delay); - } - - if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { - this._mouseStarted = (this._mouseStart(event) !== false); - if (!this._mouseStarted) { - event.preventDefault(); - return true; - } - } - - // Click event may never have fired (Gecko & Opera) - if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { - $.removeData(event.target, this.widgetName + ".preventClickEvent"); - } - - // these delegates are required to keep context - this._mouseMoveDelegate = function(event) { - return that._mouseMove(event); - }; - this._mouseUpDelegate = function(event) { - return that._mouseUp(event); - }; - - this.document - .bind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .bind( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - event.preventDefault(); - - mouseHandled = true; - return true; - }, - - _mouseMove: function(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) { - this._mouseDrag(event); - return event.preventDefault(); - } - - if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { - this._mouseStarted = - (this._mouseStart(this._mouseDownEvent, event) !== false); - (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); - } - - return !this._mouseStarted; - }, - - _mouseUp: function(event) { - this.document - .unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .unbind( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - if (this._mouseStarted) { - this._mouseStarted = false; - - if (event.target === this._mouseDownEvent.target) { - $.data(event.target, this.widgetName + ".preventClickEvent", true); - } - - this._mouseStop(event); - } - - mouseHandled = false; - return false; - }, - - _mouseDistanceMet: function(event) { - return (Math.max( - Math.abs(this._mouseDownEvent.pageX - event.pageX), - Math.abs(this._mouseDownEvent.pageY - event.pageY) - ) >= this.options.distance - ); - }, - - _mouseDelayMet: function(/* event */) { - return this.mouseDelayMet; - }, - - // These are placeholder methods, to be overriden by extending plugin - _mouseStart: function(/* event */) {}, - _mouseDrag: function(/* event */) {}, - _mouseStop: function(/* event */) {}, - _mouseCapture: function(/* event */) { return true; } -}); - - -/*! - * jQuery UI Position 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/position/ - */ - -(function() { - -$.ui = $.ui || {}; - -var cachedScrollbarWidth, supportsOffsetFractions, - max = Math.max, - abs = Math.abs, - round = Math.round, - rhorizontal = /left|center|right/, - rvertical = /top|center|bottom/, - roffset = /[\+\-]\d+(\.[\d]+)?%?/, - rposition = /^\w+/, - rpercent = /%$/, - _position = $.fn.position; - -function getOffsets( offsets, width, height ) { - return [ - parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), - parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) - ]; -} - -function parseCss( element, property ) { - return parseInt( $.css( element, property ), 10 ) || 0; -} - -function getDimensions( elem ) { - var raw = elem[0]; - if ( raw.nodeType === 9 ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: 0, left: 0 } - }; - } - if ( $.isWindow( raw ) ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: elem.scrollTop(), left: elem.scrollLeft() } - }; - } - if ( raw.preventDefault ) { - return { - width: 0, - height: 0, - offset: { top: raw.pageY, left: raw.pageX } - }; - } - return { - width: elem.outerWidth(), - height: elem.outerHeight(), - offset: elem.offset() - }; -} - -$.position = { - scrollbarWidth: function() { - if ( cachedScrollbarWidth !== undefined ) { - return cachedScrollbarWidth; - } - var w1, w2, - div = $( "
" ), - innerDiv = div.children()[0]; - - $( "body" ).append( div ); - w1 = innerDiv.offsetWidth; - div.css( "overflow", "scroll" ); - - w2 = innerDiv.offsetWidth; - - if ( w1 === w2 ) { - w2 = div[0].clientWidth; - } - - div.remove(); - - return (cachedScrollbarWidth = w1 - w2); - }, - getScrollInfo: function( within ) { - var overflowX = within.isWindow || within.isDocument ? "" : - within.element.css( "overflow-x" ), - overflowY = within.isWindow || within.isDocument ? "" : - within.element.css( "overflow-y" ), - hasOverflowX = overflowX === "scroll" || - ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), - hasOverflowY = overflowY === "scroll" || - ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); - return { - width: hasOverflowY ? $.position.scrollbarWidth() : 0, - height: hasOverflowX ? $.position.scrollbarWidth() : 0 - }; - }, - getWithinInfo: function( element ) { - var withinElement = $( element || window ), - isWindow = $.isWindow( withinElement[0] ), - isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9; - return { - element: withinElement, - isWindow: isWindow, - isDocument: isDocument, - offset: withinElement.offset() || { left: 0, top: 0 }, - scrollLeft: withinElement.scrollLeft(), - scrollTop: withinElement.scrollTop(), - - // support: jQuery 1.6.x - // jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows - width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(), - height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight() - }; - } -}; - -$.fn.position = function( options ) { - if ( !options || !options.of ) { - return _position.apply( this, arguments ); - } - - // make a copy, we don't want to modify arguments - options = $.extend( {}, options ); - - var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, - target = $( options.of ), - within = $.position.getWithinInfo( options.within ), - scrollInfo = $.position.getScrollInfo( within ), - collision = ( options.collision || "flip" ).split( " " ), - offsets = {}; - - dimensions = getDimensions( target ); - if ( target[0].preventDefault ) { - // force left top to allow flipping - options.at = "left top"; - } - targetWidth = dimensions.width; - targetHeight = dimensions.height; - targetOffset = dimensions.offset; - // clone to reuse original targetOffset later - basePosition = $.extend( {}, targetOffset ); - - // force my and at to have valid horizontal and vertical positions - // if a value is missing or invalid, it will be converted to center - $.each( [ "my", "at" ], function() { - var pos = ( options[ this ] || "" ).split( " " ), - horizontalOffset, - verticalOffset; - - if ( pos.length === 1) { - pos = rhorizontal.test( pos[ 0 ] ) ? - pos.concat( [ "center" ] ) : - rvertical.test( pos[ 0 ] ) ? - [ "center" ].concat( pos ) : - [ "center", "center" ]; - } - pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; - pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; - - // calculate offsets - horizontalOffset = roffset.exec( pos[ 0 ] ); - verticalOffset = roffset.exec( pos[ 1 ] ); - offsets[ this ] = [ - horizontalOffset ? horizontalOffset[ 0 ] : 0, - verticalOffset ? verticalOffset[ 0 ] : 0 - ]; - - // reduce to just the positions without the offsets - options[ this ] = [ - rposition.exec( pos[ 0 ] )[ 0 ], - rposition.exec( pos[ 1 ] )[ 0 ] - ]; - }); - - // normalize collision option - if ( collision.length === 1 ) { - collision[ 1 ] = collision[ 0 ]; - } - - if ( options.at[ 0 ] === "right" ) { - basePosition.left += targetWidth; - } else if ( options.at[ 0 ] === "center" ) { - basePosition.left += targetWidth / 2; - } - - if ( options.at[ 1 ] === "bottom" ) { - basePosition.top += targetHeight; - } else if ( options.at[ 1 ] === "center" ) { - basePosition.top += targetHeight / 2; - } - - atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); - basePosition.left += atOffset[ 0 ]; - basePosition.top += atOffset[ 1 ]; - - return this.each(function() { - var collisionPosition, using, - elem = $( this ), - elemWidth = elem.outerWidth(), - elemHeight = elem.outerHeight(), - marginLeft = parseCss( this, "marginLeft" ), - marginTop = parseCss( this, "marginTop" ), - collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, - collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, - position = $.extend( {}, basePosition ), - myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); - - if ( options.my[ 0 ] === "right" ) { - position.left -= elemWidth; - } else if ( options.my[ 0 ] === "center" ) { - position.left -= elemWidth / 2; - } - - if ( options.my[ 1 ] === "bottom" ) { - position.top -= elemHeight; - } else if ( options.my[ 1 ] === "center" ) { - position.top -= elemHeight / 2; - } - - position.left += myOffset[ 0 ]; - position.top += myOffset[ 1 ]; - - // if the browser doesn't support fractions, then round for consistent results - if ( !supportsOffsetFractions ) { - position.left = round( position.left ); - position.top = round( position.top ); - } - - collisionPosition = { - marginLeft: marginLeft, - marginTop: marginTop - }; - - $.each( [ "left", "top" ], function( i, dir ) { - if ( $.ui.position[ collision[ i ] ] ) { - $.ui.position[ collision[ i ] ][ dir ]( position, { - targetWidth: targetWidth, - targetHeight: targetHeight, - elemWidth: elemWidth, - elemHeight: elemHeight, - collisionPosition: collisionPosition, - collisionWidth: collisionWidth, - collisionHeight: collisionHeight, - offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], - my: options.my, - at: options.at, - within: within, - elem: elem - }); - } - }); - - if ( options.using ) { - // adds feedback as second argument to using callback, if present - using = function( props ) { - var left = targetOffset.left - position.left, - right = left + targetWidth - elemWidth, - top = targetOffset.top - position.top, - bottom = top + targetHeight - elemHeight, - feedback = { - target: { - element: target, - left: targetOffset.left, - top: targetOffset.top, - width: targetWidth, - height: targetHeight - }, - element: { - element: elem, - left: position.left, - top: position.top, - width: elemWidth, - height: elemHeight - }, - horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", - vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" - }; - if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { - feedback.horizontal = "center"; - } - if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { - feedback.vertical = "middle"; - } - if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { - feedback.important = "horizontal"; - } else { - feedback.important = "vertical"; - } - options.using.call( this, props, feedback ); - }; - } - - elem.offset( $.extend( position, { using: using } ) ); - }); -}; - -$.ui.position = { - fit: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, - outerWidth = within.width, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = withinOffset - collisionPosLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, - newOverRight; - - // element is wider than within - if ( data.collisionWidth > outerWidth ) { - // element is initially over the left side of within - if ( overLeft > 0 && overRight <= 0 ) { - newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; - position.left += overLeft - newOverRight; - // element is initially over right side of within - } else if ( overRight > 0 && overLeft <= 0 ) { - position.left = withinOffset; - // element is initially over both left and right sides of within - } else { - if ( overLeft > overRight ) { - position.left = withinOffset + outerWidth - data.collisionWidth; - } else { - position.left = withinOffset; - } - } - // too far left -> align with left edge - } else if ( overLeft > 0 ) { - position.left += overLeft; - // too far right -> align with right edge - } else if ( overRight > 0 ) { - position.left -= overRight; - // adjust based on position and margin - } else { - position.left = max( position.left - collisionPosLeft, position.left ); - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollTop : within.offset.top, - outerHeight = data.within.height, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = withinOffset - collisionPosTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, - newOverBottom; - - // element is taller than within - if ( data.collisionHeight > outerHeight ) { - // element is initially over the top of within - if ( overTop > 0 && overBottom <= 0 ) { - newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; - position.top += overTop - newOverBottom; - // element is initially over bottom of within - } else if ( overBottom > 0 && overTop <= 0 ) { - position.top = withinOffset; - // element is initially over both top and bottom of within - } else { - if ( overTop > overBottom ) { - position.top = withinOffset + outerHeight - data.collisionHeight; - } else { - position.top = withinOffset; - } - } - // too far up -> align with top - } else if ( overTop > 0 ) { - position.top += overTop; - // too far down -> align with bottom edge - } else if ( overBottom > 0 ) { - position.top -= overBottom; - // adjust based on position and margin - } else { - position.top = max( position.top - collisionPosTop, position.top ); - } - } - }, - flip: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.offset.left + within.scrollLeft, - outerWidth = within.width, - offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = collisionPosLeft - offsetLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, - myOffset = data.my[ 0 ] === "left" ? - -data.elemWidth : - data.my[ 0 ] === "right" ? - data.elemWidth : - 0, - atOffset = data.at[ 0 ] === "left" ? - data.targetWidth : - data.at[ 0 ] === "right" ? - -data.targetWidth : - 0, - offset = -2 * data.offset[ 0 ], - newOverRight, - newOverLeft; - - if ( overLeft < 0 ) { - newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; - if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { - position.left += myOffset + atOffset + offset; - } - } else if ( overRight > 0 ) { - newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; - if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { - position.left += myOffset + atOffset + offset; - } - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.offset.top + within.scrollTop, - outerHeight = within.height, - offsetTop = within.isWindow ? within.scrollTop : within.offset.top, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = collisionPosTop - offsetTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, - top = data.my[ 1 ] === "top", - myOffset = top ? - -data.elemHeight : - data.my[ 1 ] === "bottom" ? - data.elemHeight : - 0, - atOffset = data.at[ 1 ] === "top" ? - data.targetHeight : - data.at[ 1 ] === "bottom" ? - -data.targetHeight : - 0, - offset = -2 * data.offset[ 1 ], - newOverTop, - newOverBottom; - if ( overTop < 0 ) { - newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; - if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { - position.top += myOffset + atOffset + offset; - } - } else if ( overBottom > 0 ) { - newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; - if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { - position.top += myOffset + atOffset + offset; - } - } - } - }, - flipfit: { - left: function() { - $.ui.position.flip.left.apply( this, arguments ); - $.ui.position.fit.left.apply( this, arguments ); - }, - top: function() { - $.ui.position.flip.top.apply( this, arguments ); - $.ui.position.fit.top.apply( this, arguments ); - } - } -}; - -// fraction support test -(function() { - var testElement, testElementParent, testElementStyle, offsetLeft, i, - body = document.getElementsByTagName( "body" )[ 0 ], - div = document.createElement( "div" ); - - //Create a "fake body" for testing based on method used in jQuery.support - testElement = document.createElement( body ? "div" : "body" ); - testElementStyle = { - visibility: "hidden", - width: 0, - height: 0, - border: 0, - margin: 0, - background: "none" - }; - if ( body ) { - $.extend( testElementStyle, { - position: "absolute", - left: "-1000px", - top: "-1000px" - }); - } - for ( i in testElementStyle ) { - testElement.style[ i ] = testElementStyle[ i ]; - } - testElement.appendChild( div ); - testElementParent = body || document.documentElement; - testElementParent.insertBefore( testElement, testElementParent.firstChild ); - - div.style.cssText = "position: absolute; left: 10.7432222px;"; - - offsetLeft = $( div ).offset().left; - supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11; - - testElement.innerHTML = ""; - testElementParent.removeChild( testElement ); -})(); - -})(); - -var position = $.ui.position; - - -/*! - * 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.11.4", - widgetEventPrefix: "drag", - options: { - addClasses: true, - appendTo: "parent", - axis: false, - connectToSortable: false, - containment: false, - cursor: "auto", - cursorAt: false, - grid: false, - handle: false, - helper: "original", - iframeFix: false, - opacity: false, - refreshPositions: false, - revert: false, - revertDuration: 500, - scope: "default", - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - snap: false, - snapMode: "both", - snapTolerance: 20, - stack: false, - zIndex: false, - - // callbacks - drag: null, - start: null, - stop: null - }, - _create: function() { - - if ( this.options.helper === "original" ) { - this._setPositionRelative(); - } - if (this.options.addClasses){ - this.element.addClass("ui-draggable"); - } - 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; - } - - //Quit if we're not on a valid handle - this.handle = this._getHandle(event); - if (!this.handle) { - return false; - } - - 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