//根据步骤内容项的绝对索引位置,获取相对于步骤项的位置 //step从0开始,pane表示绝对索引位置,比如stepPanes一共有6个,那么pane可能的值就是0-5 //举例:config: [1,3,1,1], step: 2, pane: 4,就会返回1,表示第三个步骤的第1个步骤内容项的位置 StepJump.getRelativePaneIndex = function(config, step, pane) { return pane - getPaneCountBeforeStep(config, step) + 1; };
因为前面那些回调传递的参数都是物理索引,外部如果需要把物理索引转换成逻辑索引的话,就得使用这个方法。
其它细节说明:
1)maxStepIndex
这个变量也很关键,通过它来控制哪些大步骤不能通过直接点击的方式来跳转。
2)大步骤项的UI控制
//步骤项UI控制 function showStep(targetStep) { $navSteps.each(function(i) { var cname = this.className; cname = $.trim(cname.replace(/current|done/g, '')); if (i < targetStep) { //当前步骤之前的状态全部设置为done cname += ' done'; } else if (i == targetStep) { //当前步骤项状态设置为current cname += ' current'; } this.className = cname; }); }
整体实现如下,代码优化程度受水平限制,但是逻辑还是很清楚的:
define(function(require, exports, module) { var $ = require('jquery'); //step: 表示步骤项 //pane: 表示步骤内容项 var DEFAULTS = { config: [], //必传参数,步骤项与步骤内容项的配置,如[1,2,3]表示一共有三个(config.length)步骤,第1个步骤有1个(config[0])内容项,第2个步骤有2个(config[1])内容项,第3个步骤有3个(config[2])内容项 stepPanes: '', //必传参数,步骤内容项的jq 选择器 navSteps: '', //必传参数,步骤项的jq 选择器 initStepIndex: 1, //初始时显示的步骤位置,如果一共有4个步骤,该参数可选值为:1,2,3,4 initPaneIndex: 1, //初始时显示的步骤内容项位置,基于initStepIndex,如果initStepIndex设置成2,且该步骤有3个内容项,则该参数可选值为:1,2,3 onStepJump: $.noop, //步骤项跳转时候的回调 onBeforePaneChange: $.noop, //步骤内容项切换之前的回调 onPaneChange: $.noop, //步骤内容项切换之后的回调 onPaneLoad: $.noop //步骤内容项第一次显示时的回调 }; function StepJump(options) { var opts = $.extend({}, DEFAULTS, options), $stepPanes = $(opts.stepPanes), $navSteps = $(opts.navSteps), config = opts.config, stepPaneCount = sum.apply(null, config), //步骤内容项的总数 currentStep = opts.initStepIndex - 1, //当前步骤项的索引 currentPane = sum.apply(null, config.slice(0, currentStep)) + (opts.initPaneIndex - 1), //当前内容项的索引 maxStepIndex = currentStep, //允许通过直接点击步骤项跳转的最大步骤项位置 $activePane = $stepPanes.eq(currentPane); //注册仅触发一次的stepLoad事件 $stepPanes.each(function() { $(this).one('stepLoad', $.proxy(function() { opts.onPaneLoad.apply(this, [].slice.apply(arguments).concat([currentStep, currentPane])); }, this)); }); //初始化UI showStep(currentStep); $activePane.addClass('active').trigger('stepLoad'); //注册点击步骤项的回调 $navSteps.on('click.step.jump', function() { var $this = $(this), step = $this.index(opts.navSteps); //找到当前点击步骤项在所有步骤项中的位置 if (step > maxStepIndex || $this.hasClass('current')) return; //跳转到该步骤项的第一个步骤内容项 goStep(step); }); //步骤项UI控制 function showStep(targetStep) { $navSteps.each(function(i) { var cname = this.className; cname = $.trim(cname.replace(/current|done/g, '')); if (i < targetStep) { //当前步骤之前的状态全部设置为done cname += ' done'; } else if (i == targetStep) { //当前步骤项状态设置为current cname += ' current'; } this.className = cname; }); } function goStep(step) { go(getPaneCountBeforeStep(config, step)); } //通过步骤内容项查找步骤项的位置 function getStepByPaneIndex(targetPane) { var r = 0, targetStep = 0; for (var i = 0; i < stepPaneCount; i++) { r = r + config[i]; if (targetPane < r) { targetStep = i; break; } } return targetStep; } function go(targetPane) { if (targetPane < 0 || targetPane >= stepPaneCount) { return; } //在切换步骤内容项之前提供给外部的回调,以便外部可以对当前步骤内容项做一些校验之类的工作 //如果回调返回false则取消切换 var ret = opts.onBeforePaneChange(currentPane, targetPane, currentStep); if (ret === false) return; var $targetPane = $stepPanes.eq(targetPane), targetStep = getStepByPaneIndex(targetPane); $activePane.removeClass('active'); $targetPane.addClass('active'); opts.onPaneChange(currentPane, targetPane, currentStep); $activePane = $targetPane; currentPane = targetPane; var oldStepIndex = currentStep; currentStep = targetStep; currentStep > maxStepIndex && (maxStepIndex = currentStep); $targetPane.trigger('stepLoad'); if (targetStep !== oldStepIndex) { showStep(targetStep); opts.onStepJump(oldStepIndex, targetStep); } } return { goStep: function(step) { goStep(step - 1); }, goNext: function() { go(currentPane + 1); }, goPrev: function() { go(currentPane - 1); } } } //根据步骤内容项的绝对索引位置,获取相对于步骤项的位置 //step从0开始,pane表示绝对索引位置,比如stepPanes一共有6个,那么pane可能的值就是0-5 //举例:config: [1,3,1,1], step: 2, pane: 4,就会返回1,表示第三个步骤的第1个步骤内容项的位置 StepJump.getRelativePaneIndex = function(config, step, pane) { return pane - getPaneCountBeforeStep(config, step) + 1; }; //求和 //注:slice(start,end)返回的数据不包含end索引对应的元素 function sum() { var a = [].slice.apply(arguments), r = 0; a.forEach(function(n) { r = r + n; }); return r; } //统计在指定的步骤项之前一共有多少个步骤内容项,step从0开始,比如config: [1,3,1,1], 当step=2,就会返回4 function getPaneCountBeforeStep(config, step) { return sum.apply(null, config.slice(0, step)); } return StepJump; });
4. 调用举例
demo.html里的使用方式: