Added swipe for navigation in Wizard,
clarified step progress Added loading circle on Wizard Change-Id: I42a5ab759a8ebe4599fe6a8d896cbe56b16952d0
This commit is contained in:
parent
d6f9ce44a3
commit
723190803f
@ -549,3 +549,15 @@ div.center-align {
|
||||
min-height: 50px;
|
||||
padding: 10px;
|
||||
}
|
||||
.wizard-steps li.complete:before, .wizard-steps li.complete .step {
|
||||
/* border-color: #93cbf9 !important;*/}
|
||||
.a.wizard-steps li.active:before, .wizard-steps li.active .step {
|
||||
border-color: #80afd4 !important;
|
||||
color: #87ba21 !important;
|
||||
transform: scale(1.25) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
li.incomplete:before, li.incomplete .step {
|
||||
border-color: #d15b47 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -17,9 +17,11 @@
|
||||
<script type="text/javascript" src="vendor/jquery/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
|
||||
|
||||
<!-- AngularJS v1.2.16 -->
|
||||
<script type="text/javascript" src="vendor/angular/angular.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular-animate.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular-mocks.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular-touch.js"></script>
|
||||
|
||||
<!--<script type="text/javascript" src="../vendor/angular/angular-resource.min.js"></script>-->
|
||||
<script type="text/javascript" src="vendor/angular-ui-router/angular-ui-router.min.js"></script>
|
||||
@ -30,6 +32,10 @@
|
||||
<script type="text/javascript" src="vendor/rickshaw/rickshaw.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular-rickshaw/rickshaw.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="vendor/angular-spinner/spin.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular-spinner/angular-spinner.min.js"></script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="src/app/app.js"></script>
|
||||
<script type="text/javascript" src="src/app/appDev.js"></script>
|
||||
<script type="text/javascript" src="src/app/login/login.js"></script>
|
||||
|
@ -8,6 +8,7 @@ var app = angular.module('compass', [
|
||||
'compass.server',
|
||||
'ui.router',
|
||||
'ui.bootstrap',
|
||||
//'compassAppDev',
|
||||
'ngAnimate'
|
||||
]);
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<h4>Networks</h4>
|
||||
</div>
|
||||
<br>
|
||||
<p>Use drag and drop to move logical networks between physical interfaces. (Interface assigned Promisc Mode can only map with public network)</p>
|
||||
<p>Use drag and drop to move logical networks between physical interfaces. (Interface assigned Promisc Mode can only map with Public network)</p>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="widget-main network-background">
|
||||
|
@ -3,7 +3,9 @@ angular.module('compass.wizard', [
|
||||
'ui.bootstrap',
|
||||
'ngTable',
|
||||
'compass.charts',
|
||||
'ngDragDrop'
|
||||
'ngDragDrop',
|
||||
'ngTouch',
|
||||
'angularSpinner'
|
||||
])
|
||||
|
||||
.config(function config($stateProvider) {
|
||||
@ -33,7 +35,8 @@ angular.module('compass.wizard', [
|
||||
});
|
||||
})
|
||||
|
||||
.controller('wizardCtrl', function($scope, dataService, wizardFactory, $stateParams, $state, clusterData, machinesHostsData) {
|
||||
.controller('wizardCtrl', function($scope, dataService, wizardFactory, $stateParams, $state, clusterData, machinesHostsData, usSpinnerService) {
|
||||
|
||||
$scope.clusterId = $stateParams.id;
|
||||
$scope.cluster = clusterData;
|
||||
wizardFactory.setClusterInfo($scope.cluster);
|
||||
@ -46,8 +49,22 @@ angular.module('compass.wizard', [
|
||||
});
|
||||
}
|
||||
|
||||
// current step for create-cluster wizard
|
||||
$scope.currentStep = 1;
|
||||
$scope.pendingStep = 1;
|
||||
$scope.maxStep = 1;
|
||||
|
||||
// Functions used only in HTML
|
||||
$scope.startSpin = function() {
|
||||
usSpinnerService.spin('spinner-1');
|
||||
}
|
||||
|
||||
$scope.stopSpin = function() {
|
||||
usSpinnerService.stop('spinner-1');
|
||||
}
|
||||
$scope.testSpinClick = function() {
|
||||
//usSpinnerService.stop('spinner-1');
|
||||
usSpinnerService.spin('spinner-1');
|
||||
}
|
||||
|
||||
// get the wizard steps for create-cluster
|
||||
dataService.getWizardSteps().success(function(data) {
|
||||
@ -55,19 +72,37 @@ angular.module('compass.wizard', [
|
||||
$scope.steps = data["os_and_ts"];
|
||||
wizardFactory.setSteps($scope.steps);
|
||||
|
||||
// change ui steps css if currentStep changes
|
||||
$scope.$watch('currentStep', function(newStep, oldStep) {
|
||||
if(newStep != oldStep) {
|
||||
if (newStep > 0 && newStep <= $scope.steps.length) {
|
||||
if (newStep > oldStep) {
|
||||
$scope.steps[newStep - 1].state = "active";
|
||||
for (var i = 0; i < newStep - 1; i++)
|
||||
$scope.steps[i].state = "complete";
|
||||
} else if (newStep < oldStep) {
|
||||
$scope.steps[newStep - 1].state = "active";
|
||||
for (var j = newStep; j < $scope.steps.length; j++)
|
||||
$scope.steps[j].state = "";
|
||||
$scope.$watch('pendingStep', function(newStep, oldStep) {
|
||||
if (newStep != oldStep) {
|
||||
if ($scope.pendingStep <= $scope.maxStep + 1) {
|
||||
if ((($scope.pendingStep == 6 || $scope.pendingStep == 7) && $scope.steps[2].state == "incomplete") || (($scope.pendingStep == 7 || $scope.pendingStep == 8) && $scope.steps[5].state == "incomplete") || ($scope.pendingStep == 8 && $scope.steps[6].state == "incomplete")) {
|
||||
usSpinnerService.stop('spinner-1');
|
||||
alert("Please make sure pre-requisite steps are complete.");
|
||||
previousStepsComplete = false;
|
||||
$scope.pendingStep = $scope.currentStep;
|
||||
return;
|
||||
} else {
|
||||
var previousStepsComplete = true;
|
||||
//usSpinnerService.spin('spinner-1');
|
||||
if (previousStepsComplete == true) {
|
||||
if (oldStep != 8) {
|
||||
var commitState = {
|
||||
"name": $scope.steps[oldStep - 1].name,
|
||||
"state": "triggered",
|
||||
"message": ""
|
||||
};
|
||||
wizardFactory.setCommitState(commitState);
|
||||
} else {
|
||||
$scope.updateStepProgress($scope.pendingStep, $scope.currentStep);
|
||||
$scope.currentStep = $scope.pendingStep;
|
||||
//usSpinnerService.stop('spinner-1');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
usSpinnerService.stop('spinner-1');
|
||||
alert("Please complete previous steps first");
|
||||
$scope.pendingStep = $scope.currentStep;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -81,56 +116,131 @@ angular.module('compass.wizard', [
|
||||
"message": ""
|
||||
};
|
||||
wizardFactory.setCommitState(commitState);
|
||||
|
||||
// watch commit state change
|
||||
$scope.$watch(function() {
|
||||
return wizardFactory.getCommitState()
|
||||
}, function(newCommitState, oldCommitState) {
|
||||
if (newCommitState != oldCommitState) {
|
||||
if (newCommitState.name == $scope.steps[$scope.currentStep - 1].name && newCommitState.state == "success") {
|
||||
console.warn("### catch success in wizardCtrl ###", newCommitState, oldCommitState);
|
||||
$scope.next();
|
||||
$scope.alert = "";
|
||||
} else if (newCommitState.state == "error") {
|
||||
// TODO: error handling / display error message
|
||||
console.warn("### catch error in wizardCtrl ###", newCommitState, oldCommitState);
|
||||
$scope.alerts = [];
|
||||
$scope.alerts.push(newCommitState.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
$scope.next = function() {
|
||||
if ($scope.currentStep < $scope.steps.length)
|
||||
$scope.currentStep = $scope.currentStep + 1;
|
||||
else if ($scope.currentStep == $scope.steps.length) {
|
||||
$state.go("cluster.overview", {
|
||||
'id': $scope.cluster.id
|
||||
});
|
||||
// Watch commit state change
|
||||
$scope.$watch(function() {
|
||||
return wizardFactory.getCommitState()
|
||||
}, function(newCommitState, oldCommitState) {
|
||||
if ((newCommitState != oldCommitState) && (newCommitState.name == $scope.steps[$scope.currentStep - 1].name)) {
|
||||
if (newCommitState.state == "success") {
|
||||
console.warn("### catch success in wizardCtrl ###", newCommitState, oldCommitState);
|
||||
$scope.alert = "";
|
||||
if (newCommitState.name == "review") {
|
||||
$state.go("cluster.overview", {
|
||||
'id': $scope.cluster.id
|
||||
});
|
||||
}
|
||||
$scope.updateStepProgress($scope.pendingStep, $scope.currentStep);
|
||||
$scope.currentStep = $scope.pendingStep;
|
||||
if ($scope.currentStep > $scope.maxStep) {
|
||||
$scope.maxStep = $scope.currentStep;
|
||||
}
|
||||
//usSpinnerService.stop('spinner-1');
|
||||
} else if (newCommitState.state == "error") {
|
||||
$scope.pendingStep = $scope.currentStep;
|
||||
// TODO: error handling / display error message
|
||||
console.warn("### catch error in wizardCtrl ###", newCommitState, oldCommitState);
|
||||
$scope.alerts = [];
|
||||
$scope.alerts.push(newCommitState.message);
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Updates CSS Classes on Step state change
|
||||
$scope.updateStepProgress = function(newStep, oldStep) {
|
||||
$scope.steps[newStep - 1].state = "active";
|
||||
$scope.steps[oldStep - 1].state = "complete";
|
||||
if (newStep == 1) {
|
||||
if ($scope.maxStep > 2) {
|
||||
$scope.steps[2].state = "incomplete";
|
||||
}
|
||||
if ($scope.maxStep > 5) {
|
||||
$scope.steps[5].state = "incomplete";
|
||||
}
|
||||
if ($scope.maxStep > 6) {
|
||||
$scope.steps[6].state = "incomplete";
|
||||
}
|
||||
}
|
||||
if (newStep == 3) {
|
||||
if ($scope.maxStep > 5) {
|
||||
$scope.steps[5].state = "incomplete";
|
||||
}
|
||||
if ($scope.maxStep > 6) {
|
||||
$scope.steps[6].state = "incomplete";
|
||||
}
|
||||
}
|
||||
if (newStep == 6) {
|
||||
if ($scope.maxStep > 6) {
|
||||
$scope.steps[6].state = "incomplete";
|
||||
}
|
||||
}
|
||||
if (oldStep == 8) {
|
||||
$scope.steps[7].state = "";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.stepForward = function() {
|
||||
if ($scope.currentStep == $scope.steps.length) {
|
||||
usSpinnerService.stop('spinner-1');
|
||||
}
|
||||
if ($scope.currentStep != $scope.steps.length) {
|
||||
if ($scope.pendingStep < $scope.steps.length)
|
||||
$scope.pendingStep = $scope.pendingStep + 1;
|
||||
}
|
||||
};
|
||||
|
||||
// go to previous step
|
||||
$scope.stepBackward = function() {
|
||||
if ($scope.currentStep > 1) {
|
||||
$scope.currentStep = $scope.currentStep - 1;
|
||||
if ($scope.currentStep == 1) {
|
||||
usSpinnerService.stop('spinner-1');
|
||||
}
|
||||
if ($scope.pendingStep > 1) {
|
||||
$scope.pendingStep = $scope.pendingStep - 1;
|
||||
}
|
||||
};
|
||||
// go to step by stepId
|
||||
$scope.skipForward = function(stepId) {
|
||||
$scope.pendingStep = stepId;
|
||||
if ($scope.pendingStep == $scope.currentStep) {
|
||||
usSpinnerService.stop('spinner-1');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.deploy = function() {
|
||||
var wizard_complete = true;
|
||||
for (var i = 0; i < $scope.steps.length; i++)
|
||||
if ($scope.steps[i].state == "incomplete") {
|
||||
wizard_complete = false;
|
||||
}
|
||||
|
||||
if (wizard_complete == true) {
|
||||
var commitState = {
|
||||
"name": $scope.steps[$scope.currentStep - 1].name,
|
||||
"state": "triggered",
|
||||
"message": ""
|
||||
};
|
||||
wizardFactory.setCommitState(commitState);
|
||||
}
|
||||
};
|
||||
|
||||
// go to step by stepId
|
||||
$scope.goToStep = function(stepId) {
|
||||
$scope.currentStep = stepId;
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
dataService.getSubnetConfig().success(function(data) {
|
||||
wizardFactory.setSubnetworks(data);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
.controller('svSelectCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService) {
|
||||
.controller('svSelectCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService, usSpinnerService) {
|
||||
$scope.hideunselected = '';
|
||||
$scope.search = {};
|
||||
|
||||
@ -138,6 +248,8 @@ angular.module('compass.wizard', [
|
||||
|
||||
$scope.allservers = wizardFactory.getAllMachinesHost();
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.tableParams = new ngTableParams({
|
||||
page: 1, // show first page
|
||||
count: $scope.allservers.length // count per page
|
||||
@ -219,11 +331,15 @@ angular.module('compass.wizard', [
|
||||
})
|
||||
if (noSelection) {
|
||||
alert("Please select at least one server");
|
||||
wizardFactory.setCommitState({});
|
||||
var commitState = {
|
||||
"name": "sv_selection",
|
||||
"state": "error",
|
||||
"message": ""
|
||||
};
|
||||
wizardFactory.setCommitState(commitState);
|
||||
} else {
|
||||
wizardFactory.setServers(selectedServers);
|
||||
wizardFactory.setAllMachinesHost($scope.allservers);
|
||||
|
||||
var commitState = {
|
||||
"name": "sv_selection",
|
||||
"state": "success",
|
||||
@ -234,7 +350,7 @@ angular.module('compass.wizard', [
|
||||
};
|
||||
})
|
||||
|
||||
.controller('globalCtrl', function($scope, wizardFactory, dataService, $q) {
|
||||
.controller('globalCtrl', function($scope, wizardFactory, dataService, $q, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
|
||||
//For General Section
|
||||
@ -250,6 +366,8 @@ angular.module('compass.wizard', [
|
||||
$scope.general["no_proxy"] = [""];
|
||||
}
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.addValue = function(key) {
|
||||
$scope.general[key].push("");
|
||||
};
|
||||
@ -390,7 +508,7 @@ angular.module('compass.wizard', [
|
||||
*/
|
||||
})
|
||||
|
||||
.controller('networkCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService, $q) {
|
||||
.controller('networkCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService, $q, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
$scope.subnetworks = wizardFactory.getSubnetworks();
|
||||
$scope.interfaces = wizardFactory.getInterfaces();
|
||||
@ -400,6 +518,8 @@ angular.module('compass.wizard', [
|
||||
$scope.server_columns = data.showless;
|
||||
});
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.tableParams = new ngTableParams({
|
||||
page: 1, // show first page
|
||||
count: $scope.servers.length // count per page
|
||||
@ -488,6 +608,7 @@ angular.module('compass.wizard', [
|
||||
"reinstall_os": server.reinstallos
|
||||
});
|
||||
}
|
||||
usSpinnerService.stop('spinner-1');
|
||||
});
|
||||
|
||||
var interfaceCount = Object.keys($scope.interfaces).length;
|
||||
@ -653,7 +774,7 @@ angular.module('compass.wizard', [
|
||||
}
|
||||
})
|
||||
|
||||
.controller('partitionCtrl', function($scope, wizardFactory, dataService) {
|
||||
.controller('partitionCtrl', function($scope, wizardFactory, dataService, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
$scope.partition = wizardFactory.getPartition();
|
||||
|
||||
@ -673,7 +794,7 @@ angular.module('compass.wizard', [
|
||||
$scope.partitionarray = [];
|
||||
var total = 0;
|
||||
angular.forEach($scope.partition, function(value, key) {
|
||||
total += parseFloat(value.percentage) ;
|
||||
total += parseFloat(value.percentage);
|
||||
$scope.partitionarray.push({
|
||||
"name": key,
|
||||
"number": value.percentage
|
||||
@ -695,6 +816,8 @@ angular.module('compass.wizard', [
|
||||
}
|
||||
});
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.commit = function() {
|
||||
var os_partition = {
|
||||
"os_config": {
|
||||
@ -720,7 +843,7 @@ angular.module('compass.wizard', [
|
||||
};
|
||||
})
|
||||
|
||||
.controller('securityCtrl', function($scope, wizardFactory, dataService) {
|
||||
.controller('securityCtrl', function($scope, wizardFactory, dataService, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
$scope.server_credentials = wizardFactory.getServerCredentials();
|
||||
$scope.service_credentials = wizardFactory.getServiceCredentials();
|
||||
@ -855,9 +978,10 @@ angular.module('compass.wizard', [
|
||||
wizardFactory.setCommitState(commitState);
|
||||
});
|
||||
};
|
||||
usSpinnerService.stop('spinner-1');
|
||||
})
|
||||
|
||||
.controller('roleAssignCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService, $q) {
|
||||
.controller('roleAssignCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService, $q, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
$scope.servers = wizardFactory.getServers();
|
||||
|
||||
@ -870,6 +994,8 @@ angular.module('compass.wizard', [
|
||||
$scope.server_columns = data.showless;
|
||||
});
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.selectAllServers = function(flag) {
|
||||
if (flag) {
|
||||
angular.forEach($scope.servers, function(sv) {
|
||||
@ -1053,11 +1179,13 @@ angular.module('compass.wizard', [
|
||||
};
|
||||
})
|
||||
|
||||
.controller('networkMappingCtrl', function($scope, wizardFactory, dataService) {
|
||||
.controller('networkMappingCtrl', function($scope, wizardFactory, dataService, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
$scope.interfaces = wizardFactory.getInterfaces();
|
||||
$scope.networking = wizardFactory.getNetworkMapping();
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.pendingInterface = "";
|
||||
|
||||
$scope.onDrop = function($event, key) {
|
||||
@ -1128,7 +1256,7 @@ angular.module('compass.wizard', [
|
||||
};
|
||||
})
|
||||
|
||||
.controller('reviewCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService) {
|
||||
.controller('reviewCtrl', function($scope, wizardFactory, dataService, $filter, ngTableParams, sortingService, usSpinnerService) {
|
||||
var cluster = wizardFactory.getClusterInfo();
|
||||
$scope.servers = wizardFactory.getServers();
|
||||
$scope.interfaces = wizardFactory.getInterfaces();
|
||||
@ -1142,6 +1270,8 @@ angular.module('compass.wizard', [
|
||||
$scope.server_columns = data.review;
|
||||
});
|
||||
|
||||
usSpinnerService.stop('spinner-1');
|
||||
|
||||
$scope.tabs = [{
|
||||
title: 'Database & Queue',
|
||||
url: 'service.tpl.html'
|
||||
@ -1239,6 +1369,5 @@ angular.module('compass.wizard', [
|
||||
console.warn("Review hosts error: ", data);
|
||||
})
|
||||
//TODO: error handling
|
||||
|
||||
};
|
||||
})
|
||||
|
@ -1,13 +1,14 @@
|
||||
<div class="main-content">
|
||||
<div class="page-content">
|
||||
<div class="page-content" ng-swipe-right="startSpin(); stepForward()" ng-swipe-left="startSpin(); stepBackward()">
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Create Cluster Wizard</h1>
|
||||
<span us-spinner="{radius:30, length: 20, lines:13, width:10}" spinner-key="spinner-1"></span>
|
||||
</div>
|
||||
|
||||
<div id="create-cluster-wizard">
|
||||
<ul class="wizard-steps">
|
||||
<li ng-repeat="step in steps" class="{{step.state}}" ng-click="goToStep(step.id)">
|
||||
<li ng-repeat="step in steps" class="{{step.state}}" ng-click="startSpin(); skipForward(step.id)">
|
||||
<span class="step">{{step.id}}</span>
|
||||
<span class="title">{{step.title}}</span>
|
||||
</li>
|
||||
@ -65,6 +66,19 @@
|
||||
<div class="gritter-bottom">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span ng-if="currentStep<steps.length">
|
||||
<button ng-click="startSpin(); stepForward()" class="btn btn-success btn-next">
|
||||
Next
|
||||
<i class="ace-icon fa fa-arrow-right icon-on-right"></i>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span ng-if="currentStep==steps.length">
|
||||
<button ng-click="deploy()" class="btn btn-success btn-next">
|
||||
<span>Deploy</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
2
v2/vendor/angular-spinner/angular-spinner.min.js
vendored
Normal file
2
v2/vendor/angular-spinner/angular-spinner.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
!function(a){"use strict";function b(a,b){a.module("angularSpinner",[]).factory("usSpinnerService",["$rootScope",function(a){var b={};return b.spin=function(b){a.$broadcast("us-spinner:spin",b)},b.stop=function(b){a.$broadcast("us-spinner:stop",b)},b}]).directive("usSpinner",["$window",function(c){return{scope:!0,link:function(d,e,f){var g=b||c.Spinner;d.spinner=null,d.key=a.isDefined(f.spinnerKey)?f.spinnerKey:!1,d.startActive=a.isDefined(f.spinnerStartActive)?f.spinnerStartActive:d.key?!1:!0,d.spin=function(){d.spinner&&d.spinner.spin(e[0])},d.stop=function(){d.spinner&&d.spinner.stop()},d.$watch(f.usSpinner,function(a){d.stop(),d.spinner=new g(a),(!d.key||d.startActive)&&d.spinner.spin(e[0])},!0),d.$on("us-spinner:spin",function(a,b){b===d.key&&d.spin()}),d.$on("us-spinner:stop",function(a,b){b===d.key&&d.stop()}),d.$on("$destroy",function(){d.stop(),d.spinner=null})}}}])}"function"==typeof define&&define.amd?define(["angular","spin"],b):b(a.angular)}(window);
|
||||
//# sourceMappingURL=angular-spinner.min.map
|
2
v2/vendor/angular-spinner/spin.min.js
vendored
Normal file
2
v2/vendor/angular-spinner/spin.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
//fgnass.github.com/spin.js#v2.0.1
|
||||
!function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d<k.length;d++)if(c=k[d]+b,void 0!==e[c])return c;return void 0!==e[b]?b:void 0}function e(a,b){for(var c in b)a.style[d(a,c)||c]=b[c];return a}function f(a){for(var b=1;b<arguments.length;b++){var c=arguments[b];for(var d in c)void 0===a[d]&&(a[d]=c[d])}return a}function g(a,b){return"string"==typeof a?a:a[b%a.length]}function h(a){this.opts=f(a||{},h.defaults,n)}function i(){function c(b,c){return a("<"+b+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d<e.childNodes.length&&(e=e.childNodes[b+d],e=e&&e.firstChild,e=e&&e.firstChild,e&&(e.opacity=c))}}var j,k=["webkit","Moz","ms","O"],l={},m=function(){var c=a("style",{type:"text/css"});return b(document.getElementsByTagName("head")[0],c),c.sheet||c.styleSheet}(),n={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",direction:1,speed:1,trail:100,opacity:.25,fps:20,zIndex:2e9,className:"spinner",top:"50%",left:"50%",position:"absolute"};h.defaults={},f(h.prototype,{spin:function(b){this.stop();{var c=this,d=c.opts,f=c.el=e(a(0,{className:d.className}),{position:d.position,width:0,zIndex:d.zIndex});d.radius+d.length+d.width}if(e(f,{left:d.left,top:d.top}),b&&b.insertBefore(f,b.firstChild||null),f.setAttribute("role","progressbar"),c.lines(f,c.opts),!j){var g,h=0,i=(d.lines-1)*(1-d.direction)/2,k=d.fps,l=k/d.speed,m=(1-d.opacity)/(l*d.trail/100),n=l/d.lines;!function o(){h++;for(var a=0;a<d.lines;a++)g=Math.max(1-(h+(d.lines-a)*n)%l*m,d.opacity),c.opacity(f,a*d.direction+i,g,d);c.timeout=c.el&&setTimeout(o,~~(1e3/k))}()}return c},stop:function(){var a=this.el;return a&&(clearTimeout(this.timeout),a.parentNode&&a.parentNode.removeChild(a),this.el=void 0),this},lines:function(d,f){function h(b,c){return e(a(),{position:"absolute",width:f.length+f.width+"px",height:f.width+"px",background:b,boxShadow:c,transformOrigin:"left",transform:"rotate("+~~(360/f.lines*k+f.rotate)+"deg) translate("+f.radius+"px,0)",borderRadius:(f.corners*f.width>>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k<f.lines;k++)i=e(a(),{position:"absolute",top:1+~(f.width/2)+"px",transform:f.hwaccel?"translate3d(0,0,0)":"",opacity:f.opacity,animation:j&&c(f.opacity,f.trail,l+k*f.direction,f.lines)+" "+1/f.speed+"s linear infinite"}),f.shadow&&b(i,e(h("#000","0 0 4px #000"),{top:"2px"})),b(d,b(i,h(g(f.color,k),"0 0 1px rgba(0,0,0,.1)")));return d},opacity:function(a,b,c){b<a.childNodes.length&&(a.childNodes[b].style.opacity=c)}});var o=e(a("group"),{behavior:"url(#default#VML)"});return!d(o,"transform")&&o.adj?i():j=d(o,"animation"),h});
|
575
v2/vendor/angular/angular-touch.js
vendored
Normal file
575
v2/vendor/angular/angular-touch.js
vendored
Normal file
@ -0,0 +1,575 @@
|
||||
/**
|
||||
* @license AngularJS v1.2.16
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name ngTouch
|
||||
* @description
|
||||
*
|
||||
* # ngTouch
|
||||
*
|
||||
* The `ngTouch` module provides touch events and other helpers for touch-enabled devices.
|
||||
* The implementation is based on jQuery Mobile touch event handling
|
||||
* ([jquerymobile.com](http://jquerymobile.com/)).
|
||||
*
|
||||
*
|
||||
* See {@link ngTouch.$swipe `$swipe`} for usage.
|
||||
*
|
||||
* <div doc-module-components="ngTouch"></div>
|
||||
*
|
||||
*/
|
||||
|
||||
// define ngTouch module
|
||||
/* global -ngTouch */
|
||||
var ngTouch = angular.module('ngTouch', []);
|
||||
|
||||
/* global ngTouch: false */
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $swipe
|
||||
*
|
||||
* @description
|
||||
* The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe
|
||||
* behavior, to make implementing swipe-related directives more convenient.
|
||||
*
|
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed.
|
||||
*
|
||||
* `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by
|
||||
* `ngCarousel` in a separate component.
|
||||
*
|
||||
* # Usage
|
||||
* The `$swipe` service is an object with a single method: `bind`. `bind` takes an element
|
||||
* which is to be watched for swipes, and an object with four handler functions. See the
|
||||
* documentation for `bind` below.
|
||||
*/
|
||||
|
||||
ngTouch.factory('$swipe', [function() {
|
||||
// The total distance in any direction before we make the call on swipe vs. scroll.
|
||||
var MOVE_BUFFER_RADIUS = 10;
|
||||
|
||||
function getCoordinates(event) {
|
||||
var touches = event.touches && event.touches.length ? event.touches : [event];
|
||||
var e = (event.changedTouches && event.changedTouches[0]) ||
|
||||
(event.originalEvent && event.originalEvent.changedTouches &&
|
||||
event.originalEvent.changedTouches[0]) ||
|
||||
touches[0].originalEvent || touches[0];
|
||||
|
||||
return {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $swipe#bind
|
||||
*
|
||||
* @description
|
||||
* The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
|
||||
* object containing event handlers.
|
||||
*
|
||||
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
|
||||
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
|
||||
*
|
||||
* `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
|
||||
* watching for `touchmove` or `mousemove` events. These events are ignored until the total
|
||||
* distance moved in either dimension exceeds a small threshold.
|
||||
*
|
||||
* Once this threshold is exceeded, either the horizontal or vertical delta is greater.
|
||||
* - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow.
|
||||
* - If the vertical distance is greater, this is a scroll, and we let the browser take over.
|
||||
* A `cancel` event is sent.
|
||||
*
|
||||
* `move` is called on `mousemove` and `touchmove` after the above logic has determined that
|
||||
* a swipe is in progress.
|
||||
*
|
||||
* `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
|
||||
*
|
||||
* `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
|
||||
* as described above.
|
||||
*
|
||||
*/
|
||||
bind: function(element, eventHandlers) {
|
||||
// Absolute total movement, used to control swipe vs. scroll.
|
||||
var totalX, totalY;
|
||||
// Coordinates of the start position.
|
||||
var startCoords;
|
||||
// Last event's position.
|
||||
var lastPos;
|
||||
// Whether a swipe is active.
|
||||
var active = false;
|
||||
|
||||
element.on('touchstart mousedown', function(event) {
|
||||
startCoords = getCoordinates(event);
|
||||
active = true;
|
||||
totalX = 0;
|
||||
totalY = 0;
|
||||
lastPos = startCoords;
|
||||
eventHandlers['start'] && eventHandlers['start'](startCoords, event);
|
||||
});
|
||||
|
||||
element.on('touchcancel', function(event) {
|
||||
active = false;
|
||||
eventHandlers['cancel'] && eventHandlers['cancel'](event);
|
||||
});
|
||||
|
||||
element.on('touchmove mousemove', function(event) {
|
||||
if (!active) return;
|
||||
|
||||
// Android will send a touchcancel if it thinks we're starting to scroll.
|
||||
// So when the total distance (+ or - or both) exceeds 10px in either direction,
|
||||
// we either:
|
||||
// - On totalX > totalY, we send preventDefault() and treat this as a swipe.
|
||||
// - On totalY > totalX, we let the browser handle it as a scroll.
|
||||
|
||||
if (!startCoords) return;
|
||||
var coords = getCoordinates(event);
|
||||
|
||||
totalX += Math.abs(coords.x - lastPos.x);
|
||||
totalY += Math.abs(coords.y - lastPos.y);
|
||||
|
||||
lastPos = coords;
|
||||
|
||||
if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
|
||||
if (totalY > totalX) {
|
||||
// Allow native scrolling to take over.
|
||||
active = false;
|
||||
eventHandlers['cancel'] && eventHandlers['cancel'](event);
|
||||
return;
|
||||
} else {
|
||||
// Prevent the browser from scrolling.
|
||||
event.preventDefault();
|
||||
eventHandlers['move'] && eventHandlers['move'](coords, event);
|
||||
}
|
||||
});
|
||||
|
||||
element.on('touchend mouseup', function(event) {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
/* global ngTouch: false */
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngClick
|
||||
*
|
||||
* @description
|
||||
* A more powerful replacement for the default ngClick designed to be used on touchscreen
|
||||
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
|
||||
* the click event. This version handles them immediately, and then prevents the
|
||||
* following click event from propagating.
|
||||
*
|
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed.
|
||||
*
|
||||
* This directive can fall back to using an ordinary click event, and so works on desktop
|
||||
* browsers as well as mobile.
|
||||
*
|
||||
* This directive also sets the CSS class `ng-click-active` while the element is being held
|
||||
* down (by a mouse click or touch) so you can restyle the depressed element if you wish.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngClick {@link guide/expression Expression} to evaluate
|
||||
* upon tap. (Event object is available as `$event`)
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<button ng-click="count = count + 1" ng-init="count=0">
|
||||
Increment
|
||||
</button>
|
||||
count: {{ count }}
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
ngTouch.config(['$provide', function($provide) {
|
||||
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
|
||||
// drop the default ngClick directive
|
||||
$delegate.shift();
|
||||
return $delegate;
|
||||
}]);
|
||||
}]);
|
||||
|
||||
ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
||||
function($parse, $timeout, $rootElement) {
|
||||
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
|
||||
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
|
||||
var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
|
||||
var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
|
||||
|
||||
var ACTIVE_CLASS_NAME = 'ng-click-active';
|
||||
var lastPreventedTime;
|
||||
var touchCoordinates;
|
||||
var lastLabelClickCoordinates;
|
||||
|
||||
|
||||
// TAP EVENTS AND GHOST CLICKS
|
||||
//
|
||||
// Why tap events?
|
||||
// Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
|
||||
// double-tapping, and then fire a click event.
|
||||
//
|
||||
// This delay sucks and makes mobile apps feel unresponsive.
|
||||
// So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
|
||||
// the user has tapped on something.
|
||||
//
|
||||
// What happens when the browser then generates a click event?
|
||||
// The browser, of course, also detects the tap and fires a click after a delay. This results in
|
||||
// tapping/clicking twice. So we do "clickbusting" to prevent it.
|
||||
//
|
||||
// How does it work?
|
||||
// We attach global touchstart and click handlers, that run during the capture (early) phase.
|
||||
// So the sequence for a tap is:
|
||||
// - global touchstart: Sets an "allowable region" at the point touched.
|
||||
// - element's touchstart: Starts a touch
|
||||
// (- touchmove or touchcancel ends the touch, no click follows)
|
||||
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
|
||||
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
|
||||
// - preventGhostClick() removes the allowable region the global touchstart created.
|
||||
// - The browser generates a click event.
|
||||
// - The global click handler catches the click, and checks whether it was in an allowable region.
|
||||
// - If preventGhostClick was called, the region will have been removed, the click is busted.
|
||||
// - If the region is still there, the click proceeds normally. Therefore clicks on links and
|
||||
// other elements without ngTap on them work normally.
|
||||
//
|
||||
// This is an ugly, terrible hack!
|
||||
// Yeah, tell me about it. The alternatives are using the slow click events, or making our users
|
||||
// deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
|
||||
// encapsulates this ugly logic away from the user.
|
||||
//
|
||||
// Why not just put click handlers on the element?
|
||||
// We do that too, just to be sure. The problem is that the tap event might have caused the DOM
|
||||
// to change, so that the click fires in the same position but something else is there now. So
|
||||
// the handlers are global and care only about coordinates and not elements.
|
||||
|
||||
// Checks if the coordinates are close enough to be within the region.
|
||||
function hit(x1, y1, x2, y2) {
|
||||
return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
|
||||
}
|
||||
|
||||
// Checks a list of allowable regions against a click location.
|
||||
// Returns true if the click should be allowed.
|
||||
// Splices out the allowable region from the list after it has been used.
|
||||
function checkAllowableRegions(touchCoordinates, x, y) {
|
||||
for (var i = 0; i < touchCoordinates.length; i += 2) {
|
||||
if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
|
||||
touchCoordinates.splice(i, i + 2);
|
||||
return true; // allowable region
|
||||
}
|
||||
}
|
||||
return false; // No allowable region; bust it.
|
||||
}
|
||||
|
||||
// Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
|
||||
// was called recently.
|
||||
function onClick(event) {
|
||||
if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
|
||||
return; // Too old.
|
||||
}
|
||||
|
||||
var touches = event.touches && event.touches.length ? event.touches : [event];
|
||||
var x = touches[0].clientX;
|
||||
var y = touches[0].clientY;
|
||||
// Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
|
||||
// and on the input element). Depending on the exact browser, this second click we don't want
|
||||
// to bust has either (0,0), negative coordinates, or coordinates equal to triggering label
|
||||
// click event
|
||||
if (x < 1 && y < 1) {
|
||||
return; // offscreen
|
||||
}
|
||||
if (lastLabelClickCoordinates &&
|
||||
lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) {
|
||||
return; // input click triggered by label click
|
||||
}
|
||||
// reset label click coordinates on first subsequent click
|
||||
if (lastLabelClickCoordinates) {
|
||||
lastLabelClickCoordinates = null;
|
||||
}
|
||||
// remember label click coordinates to prevent click busting of trigger click event on input
|
||||
if (event.target.tagName.toLowerCase() === 'label') {
|
||||
lastLabelClickCoordinates = [x, y];
|
||||
}
|
||||
|
||||
// Look for an allowable region containing this click.
|
||||
// If we find one, that means it was created by touchstart and not removed by
|
||||
// preventGhostClick, so we don't bust it.
|
||||
if (checkAllowableRegions(touchCoordinates, x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we didn't find an allowable region, bust the click.
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
// Blur focused form elements
|
||||
event.target && event.target.blur();
|
||||
}
|
||||
|
||||
|
||||
// Global touchstart handler that creates an allowable region for a click event.
|
||||
// This allowable region can be removed by preventGhostClick if we want to bust it.
|
||||
function onTouchStart(event) {
|
||||
var touches = event.touches && event.touches.length ? event.touches : [event];
|
||||
var x = touches[0].clientX;
|
||||
var y = touches[0].clientY;
|
||||
touchCoordinates.push(x, y);
|
||||
|
||||
$timeout(function() {
|
||||
// Remove the allowable region.
|
||||
for (var i = 0; i < touchCoordinates.length; i += 2) {
|
||||
if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
|
||||
touchCoordinates.splice(i, i + 2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, PREVENT_DURATION, false);
|
||||
}
|
||||
|
||||
// On the first call, attaches some event handlers. Then whenever it gets called, it creates a
|
||||
// zone around the touchstart where clicks will get busted.
|
||||
function preventGhostClick(x, y) {
|
||||
if (!touchCoordinates) {
|
||||
$rootElement[0].addEventListener('click', onClick, true);
|
||||
$rootElement[0].addEventListener('touchstart', onTouchStart, true);
|
||||
touchCoordinates = [];
|
||||
}
|
||||
|
||||
lastPreventedTime = Date.now();
|
||||
|
||||
checkAllowableRegions(touchCoordinates, x, y);
|
||||
}
|
||||
|
||||
// Actual linking function.
|
||||
return function(scope, element, attr) {
|
||||
var clickHandler = $parse(attr.ngClick),
|
||||
tapping = false,
|
||||
tapElement, // Used to blur the element after a tap.
|
||||
startTime, // Used to check if the tap was held too long.
|
||||
touchStartX,
|
||||
touchStartY;
|
||||
|
||||
function resetState() {
|
||||
tapping = false;
|
||||
element.removeClass(ACTIVE_CLASS_NAME);
|
||||
}
|
||||
|
||||
element.on('touchstart', function(event) {
|
||||
tapping = true;
|
||||
tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
|
||||
// Hack for Safari, which can target text nodes instead of containers.
|
||||
if(tapElement.nodeType == 3) {
|
||||
tapElement = tapElement.parentNode;
|
||||
}
|
||||
|
||||
element.addClass(ACTIVE_CLASS_NAME);
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
var touches = event.touches && event.touches.length ? event.touches : [event];
|
||||
var e = touches[0].originalEvent || touches[0];
|
||||
touchStartX = e.clientX;
|
||||
touchStartY = e.clientY;
|
||||
});
|
||||
|
||||
element.on('touchmove', function(event) {
|
||||
resetState();
|
||||
});
|
||||
|
||||
element.on('touchcancel', function(event) {
|
||||
resetState();
|
||||
});
|
||||
|
||||
element.on('touchend', function(event) {
|
||||
var diff = Date.now() - startTime;
|
||||
|
||||
var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
|
||||
((event.touches && event.touches.length) ? event.touches : [event]);
|
||||
var e = touches[0].originalEvent || touches[0];
|
||||
var x = e.clientX;
|
||||
var y = e.clientY;
|
||||
var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
|
||||
|
||||
if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
|
||||
// Call preventGhostClick so the clickbuster will catch the corresponding click.
|
||||
preventGhostClick(x, y);
|
||||
|
||||
// Blur the focused element (the button, probably) before firing the callback.
|
||||
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
|
||||
// I couldn't get anything to work reliably on Android Chrome.
|
||||
if (tapElement) {
|
||||
tapElement.blur();
|
||||
}
|
||||
|
||||
if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
|
||||
element.triggerHandler('click', [event]);
|
||||
}
|
||||
}
|
||||
|
||||
resetState();
|
||||
});
|
||||
|
||||
// Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
|
||||
// something else nearby.
|
||||
element.onclick = function(event) { };
|
||||
|
||||
// Actual click handler.
|
||||
// There are three different kinds of clicks, only two of which reach this point.
|
||||
// - On desktop browsers without touch events, their clicks will always come here.
|
||||
// - On mobile browsers, the simulated "fast" click will call this.
|
||||
// - But the browser's follow-up slow click will be "busted" before it reaches this handler.
|
||||
// Therefore it's safe to use this directive on both mobile and desktop.
|
||||
element.on('click', function(event, touchend) {
|
||||
scope.$apply(function() {
|
||||
clickHandler(scope, {$event: (touchend || event)});
|
||||
});
|
||||
});
|
||||
|
||||
element.on('mousedown', function(event) {
|
||||
element.addClass(ACTIVE_CLASS_NAME);
|
||||
});
|
||||
|
||||
element.on('mousemove mouseup', function(event) {
|
||||
element.removeClass(ACTIVE_CLASS_NAME);
|
||||
});
|
||||
|
||||
};
|
||||
}]);
|
||||
|
||||
/* global ngTouch: false */
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngSwipeLeft
|
||||
*
|
||||
* @description
|
||||
* Specify custom behavior when an element is swiped to the left on a touchscreen device.
|
||||
* A leftward swipe is a quick, right-to-left slide of the finger.
|
||||
* Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag
|
||||
* too.
|
||||
*
|
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
|
||||
* upon left swipe. (Event object is available as `$event`)
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<div ng-show="!showActions" ng-swipe-left="showActions = true">
|
||||
Some list content, like an email in the inbox
|
||||
</div>
|
||||
<div ng-show="showActions" ng-swipe-right="showActions = false">
|
||||
<button ng-click="reply()">Reply</button>
|
||||
<button ng-click="delete()">Delete</button>
|
||||
</div>
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngSwipeRight
|
||||
*
|
||||
* @description
|
||||
* Specify custom behavior when an element is swiped to the right on a touchscreen device.
|
||||
* A rightward swipe is a quick, left-to-right slide of the finger.
|
||||
* Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag
|
||||
* too.
|
||||
*
|
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
|
||||
* upon right swipe. (Event object is available as `$event`)
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<div ng-show="!showActions" ng-swipe-left="showActions = true">
|
||||
Some list content, like an email in the inbox
|
||||
</div>
|
||||
<div ng-show="showActions" ng-swipe-right="showActions = false">
|
||||
<button ng-click="reply()">Reply</button>
|
||||
<button ng-click="delete()">Delete</button>
|
||||
</div>
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
function makeSwipeDirective(directiveName, direction, eventName) {
|
||||
ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) {
|
||||
// The maximum vertical delta for a swipe should be less than 75px.
|
||||
var MAX_VERTICAL_DISTANCE = 75;
|
||||
// Vertical distance should not be more than a fraction of the horizontal distance.
|
||||
var MAX_VERTICAL_RATIO = 0.3;
|
||||
// At least a 30px lateral motion is necessary for a swipe.
|
||||
var MIN_HORIZONTAL_DISTANCE = 30;
|
||||
|
||||
return function(scope, element, attr) {
|
||||
var swipeHandler = $parse(attr[directiveName]);
|
||||
|
||||
var startCoords, valid;
|
||||
|
||||
function validSwipe(coords) {
|
||||
// Check that it's within the coordinates.
|
||||
// Absolute vertical distance must be within tolerances.
|
||||
// Horizontal distance, we take the current X - the starting X.
|
||||
// This is negative for leftward swipes and positive for rightward swipes.
|
||||
// After multiplying by the direction (-1 for left, +1 for right), legal swipes
|
||||
// (ie. same direction as the directive wants) will have a positive delta and
|
||||
// illegal ones a negative delta.
|
||||
// Therefore this delta must be positive, and larger than the minimum.
|
||||
if (!startCoords) return false;
|
||||
var deltaY = Math.abs(coords.y - startCoords.y);
|
||||
var deltaX = (coords.x - startCoords.x) * direction;
|
||||
return valid && // Short circuit for already-invalidated swipes.
|
||||
deltaY < MAX_VERTICAL_DISTANCE &&
|
||||
deltaX > 0 &&
|
||||
deltaX > MIN_HORIZONTAL_DISTANCE &&
|
||||
deltaY / deltaX < MAX_VERTICAL_RATIO;
|
||||
}
|
||||
|
||||
$swipe.bind(element, {
|
||||
'start': function(coords, event) {
|
||||
startCoords = coords;
|
||||
valid = true;
|
||||
},
|
||||
'cancel': function(event) {
|
||||
valid = false;
|
||||
},
|
||||
'end': function(coords, event) {
|
||||
if (validSwipe(coords)) {
|
||||
scope.$apply(function() {
|
||||
element.triggerHandler(eventName);
|
||||
swipeHandler(scope, {$event: event});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}]);
|
||||
}
|
||||
|
||||
// Left is negative X-coordinate, right is positive.
|
||||
makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft');
|
||||
makeSwipeDirective('ngSwipeRight', 1, 'swiperight');
|
||||
|
||||
|
||||
|
||||
})(window, window.angular);
|
Loading…
Reference in New Issue
Block a user