CRM-15578 - crmUiWizard - Basic implementation
authorTim Otten <totten@civicrm.org>
Thu, 30 Oct 2014 05:38:00 +0000 (22:38 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 11 Nov 2014 00:19:36 +0000 (16:19 -0800)
js/angular-crm-ui.js
partials/crmMailing2/edit.html
partials/crmUi/wizard.html [new file with mode: 0644]

index 571f56a699147558855bf100f4c4abd73a013d69..ba90939ac01f9a91b339756f4e0066a1ac51b7d6 100644 (file)
@@ -2,6 +2,11 @@
 (function (angular, $, _) {
   var idCount = 0;
 
+  var partialUrl = function (relPath) {
+    return CRM.resourceUrls['civicrm'] + '/partials/crmUi/' + relPath;
+  };
+
+
   angular.module('crmUi', [])
 
     // example <div crm-ui-accordion crm-title="ts('My Title')" crm-collapsed="true">...content...</div>
       };
     })
 
-    // example: <div crm-ui-wizard><div crm-ui-wizard-step crm-title="Step 1">...</div><div crm-ui-wizard-step crm-title="Step 2">...</div></div>
+    // example: <div crm-ui-wizard="myWizardCtrl"><div crm-ui-wizard-step crm-title="ts('Step 1')">...</div><div crm-ui-wizard-step crm-title="ts('Step 2')">...</div></div>
+    // Note: "myWizardCtrl" has various actions/properties like next() and $first().
+    // WISHLIST: Allow each step to determine if it is "complete" / "valid" / "selectable"
+    // WISHLIST: Allow each step to enable/disable (show/hide) itself
     .directive('crmUiWizard', function() {
       return {
-        template: '<div><span ng-transclude/></div>',
+        restrict: 'EA',
+        scope: {
+          crmUiWizard: '@'
+        },
+        templateUrl: partialUrl('wizard.html'),
         transclude: true,
+        controllerAs: 'crmUiWizardCtrl',
+        controller: function($scope, $parse) {
+          var steps = $scope.steps = []; // array<$scope>
+          var crmUiWizardCtrl = this;
+          var maxVisited = 0;
+          var selectedIndex = null;
+
+          var findIndex = function() {
+            var found = null;
+            angular.forEach(steps, function(step, stepKey) {
+              if (step.selected) found = stepKey;
+            });
+            return found;
+          };
+
+          /// @return int the index of the current step
+          this.$index = function() { return selectedIndex; };
+          /// @return bool whether the currentstep is first
+          this.$first = function() { return this.$index() === 0; };
+          /// @return bool whether the current step is last
+          this.$last = function() { return this.$index() === steps.length -1; };
+          this.$maxVisit = function() { return maxVisited; }
+          this.iconFor = function(index) {
+            if (index < this.$index()) return '√';
+            if (index === this.$index()) return '»';
+            return ' ';
+          }
+          this.isSelectable = function(step) {
+            if (step.selected) return false;
+            var result = false;
+            angular.forEach(steps, function(otherStep, otherKey) {
+              if (step === otherStep && otherKey <= maxVisited) result = true;
+            });
+            return result;
+          };
+
+          /*** @param Object step the $scope of the step */
+          this.select = function(step) {
+            angular.forEach(steps, function(otherStep, otherKey) {
+              otherStep.selected = (otherStep === step);
+              if (otherStep === step && maxVisited < otherKey) maxVisited = otherKey;
+            });
+            selectedIndex = findIndex();
+          };
+          /*** @param Object step the $scope of the step */
+          this.add = function(step) {
+            if (steps.length === 0) {
+              step.selected = true;
+              selectedIndex = 0;
+            }
+            steps.push(step);
+          };
+          this.goto = function(index) {
+            if (index < 0) index = 0;
+            if (index >= steps.length) index = steps.length-1;
+            this.select(steps[index]);
+          };
+          this.previous = function() { this.goto(this.$index()-1); };
+          this.next = function() { this.goto(this.$index()+1); };
+          if ($scope.crmUiWizard) {
+            $parse($scope.crmUiWizard).assign($scope.$parent, this)
+          }
+        },
         link: function (scope, element, attrs) {}
       };
     })
 
-    .directive('crmUiWizardFooter', function() {
+    // Use this to add extra markup to wizard
+    .directive('crmUiWizardButtons', function() {
       return {
-        template: '<div><span ng-transclude/></div>',
+        require: '^crmUiWizard',
+        restrict: 'EA',
+        scope: {},
+        template: '<span ng-transclude></span>',
         transclude: true,
-        link: function (scope, element, attrs) {}
+        link: function (scope, element, attrs, crmUiWizardCtrl) {
+          var realButtonsEl = $(element).closest('.crm-wizard').find('.crm-wizard-buttons');
+          $(element).appendTo(realButtonsEl);
+        }
       };
     })
 
     // example <div crm-ui-wizard-step crm-title="ts('My Title')">...content...</div>
     .directive('crmUiWizardStep', function() {
       return {
+        require: '^crmUiWizard',
+        restrict: 'EA',
         scope: {
           crmTitle: '@'
         },
-        template: '<div><b>(Step: {{$parent.$eval(crmTitle)}})</b><span ng-transclude/></div>',
+        template: '<div class="crm-wizard-step" ng-show="selected" ng-transclude/></div>',
         transclude: true,
-        link: function (scope, element, attrs) {}
+        link: function (scope, element, attrs, crmUiWizardCtrl) {
+          crmUiWizardCtrl.add(scope);
+        }
       };
     })
 
index 35bafd955c8361505dde25ac22d0c4e8a2ca6af2..ffdfb59e6f9d0dcb6e9598bf609a4fd2dbc4f919 100644 (file)
                 gotoListing();">Schedule</button>
     </div>
   </div>
-  <div crm-ui-wizard-footer>
-    <button ng-click="mailing.save();
-              gotoListing();">Save Draft</button>
+  <span crm-ui-wizard-buttons style="float:right;">
     <button ng-click="mailing.destroy();
               gotoListing();">Abort</button>
-  </div>
-
-</div>
+    <button ng-click="mailing.save();
+              gotoListing();">Save Draft</button>
+  </span>
+</div>
\ No newline at end of file
diff --git a/partials/crmUi/wizard.html b/partials/crmUi/wizard.html
new file mode 100644 (file)
index 0000000..524e3a8
--- /dev/null
@@ -0,0 +1,15 @@
+<div class="crm-wizard">
+  <ul class="crm-wizard-nav wizard-bar">
+    <li ng-repeat="step in steps" ng-class="{'current-step':step.selected}">
+      <span>{{crmUiWizardCtrl.iconFor($index)}}</span>
+      <a href="" ng-click="crmUiWizardCtrl.select(step)" ng-show="crmUiWizardCtrl.isSelectable(step)">{{1 + $index}}. {{step.$parent.$eval(step.crmTitle)}}</a>
+      <span ng-show="!crmUiWizardCtrl.isSelectable(step)">{{1 + $index}}. {{step.$parent.$eval(step.crmTitle)}}</span>
+      <!-- Don't know a good way to localize text like "1. Title" -->
+    </li>
+  </ul>
+  <div class="crm-wizard-body" ng-transclude/>
+  <div class="crm-wizard-buttons">
+    <button ng-click="crmUiWizardCtrl.previous()" ng-show="!crmUiWizardCtrl.$first()">Previous</button>
+    <button ng-click="crmUiWizardCtrl.next()" ng-show="!crmUiWizardCtrl.$last()">Next</button>
+  </div>
+</div>
\ No newline at end of file