Created
December 20, 2010 20:42
-
-
Save ericf/748950 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Guide | |
| * | |
| * Provides an interactive Guide Widget (Overlay) to present content in a series of Steps. | |
| * | |
| * GuideSteps are Widgets with WidgetStdMod support and are children to a Guide. | |
| * These Steps also have WidgetPositionAlign support which is overloaded to reference related content for the Step; | |
| * meaning a GuideStep can be aligned with it’s related content on the page, and when active, | |
| * the Guide Overlay will position itself according the currently-selected Step’s defined alignment. | |
| * | |
| * Beyond providing containment for rendering GuideSteps, a Guide will only show the content of one Step at a time. | |
| * A Guide provides navigation between it’s Steps both as a navigation list rendered in the Overlay | |
| * and a ‘next’ button to select the next GuideStep child in the list. | |
| */ | |
| var Guide, | |
| GUIDE = 'guide', | |
| GuideStep, | |
| GUIDE_STEP = 'guideStep', | |
| CIRCULAR = 'circular', | |
| STEPS_NODE = 'stepsNode', | |
| NEXT_NODE = 'nextNode', | |
| CLOSE_NODE = 'closeNode', | |
| NAVIGATION = 'navigation', | |
| NAVIGATION_NODE = 'navigationNode', | |
| BOUNDING_BOX = 'boundingBox', | |
| STD_HEADER = Y.WidgetStdMod.HEADER, | |
| STD_BODY = Y.WidgetStdMod.BODY, | |
| STD_FOOTER = Y.WidgetStdMod.FOOTER, | |
| getCN = Y.ClassNameManager.getClassName, | |
| DOT = '.', | |
| STD_SECTION_CLASS_NAMES = Y.WidgetStdMod.SECTION_CLASS_NAMES, | |
| NEXT = 'next', | |
| CLOSE = 'close', | |
| STEP = 'step', | |
| STEPS = 'steps', | |
| SELECTED = 'selected', | |
| YLang = Y.Lang, | |
| isString = YLang.isString, | |
| isBoolean = YLang.isBoolean; | |
| // *** GuideStep *** // | |
| GuideStep = Y.Base.create(GUIDE_STEP, Y.Widget, [Y.WidgetStdMod, Y.WidgetPosition, Y.WidgetPositionAlign, Y.WidgetChild], { | |
| // *** Prototype *** // | |
| BOUNDING_TEMPLATE : '<li><li>', | |
| CONTENT_TEMPLATE : null, | |
| // *** Lifecycle Methods *** // | |
| // *** WidgetPositionAlign Methods *** // | |
| _uiSetAlign : function(node, points) { | |
| // delegate UI alignment to Guide (parent) if this GuideStep is selected. | |
| if (this.get('selected')) { | |
| this.get('parent').align(node, points); | |
| } | |
| } | |
| // *** Public Methods *** // | |
| // *** Private Methods *** // | |
| }, { | |
| // *** Static *** // | |
| ATTRS : { | |
| strings : { | |
| value : { | |
| defLabel : 'Step ' | |
| } | |
| }, | |
| /** | |
| * @attribute label | |
| * @type {String} | |
| * @default "Step x" | |
| * @description The short-name of the GuideStep which will be used in navigation. | |
| * The initial value will be parsed from the title attribute of the .yui3-widget-hd element, | |
| * or get assigned the default value: "Step " + (index + 1), e.g. "Step 1". | |
| */ | |
| label : { | |
| validator : isString, | |
| valueFn : function () { | |
| return ( this.getString('defLabel') + (this.get('index') + 1) ); | |
| } | |
| } | |
| }, | |
| HTML_PARSER : { | |
| label : function (srcNode) { | |
| return srcNode.one(DOT+STD_SECTION_CLASS_NAMES[STD_HEADER]).get('title'); | |
| } | |
| } | |
| }); | |
| // *** Guide *** // | |
| Guide = Y.Base.create(GUIDE, Y.Overlay, [Y.WidgetParent], { | |
| // *** Prototype *** // | |
| STEPS_TEMPLATE : '<ol></ol>', | |
| NEXT_TEMPLATE : '<button></button>', | |
| CLOSE_TEMPLATE : '<button></button>', | |
| NAVIGATION_TEMPLATE : '<ol></ol>', | |
| NAVIGATION_ITEM_TEMPLATE : '<li><a></a></li>', | |
| _initialNextNodeContent : null, | |
| // *** Lifecycle Methods *** // | |
| initializer : function () { | |
| this._initialNextNodeContent = this.get(NEXT_NODE).getContent(); | |
| }, | |
| renderUI : function () { | |
| var boundingBox = this.get(BOUNDING_BOX); | |
| // override node:hide/show to make visiblity:hidden/visible | |
| boundingBox._hide = Y.bind(boundingBox.setStyle, boundingBox, 'visibility', 'hidden'); | |
| boundingBox._show = Y.bind(boundingBox.setStyle, boundingBox, 'visibility', 'visible'); | |
| // this is where GuideSteps are rendered. | |
| this._childrenContainer = this.get(STEPS_NODE); | |
| // Render navigation for GuideSteps. | |
| if (this.get(NAVIGATION)) { | |
| this._uiUpdateNavigation(); | |
| } | |
| }, | |
| bindUI : function () { | |
| var nav = this.get(NAVIGATION), | |
| navNode = this.get(NAVIGATION_NODE), | |
| nextNode = this.get(NEXT_NODE), | |
| closeNode = this.get(CLOSE_NODE); | |
| this.after('addChild', this._afterGuideStepAdd); | |
| this.after('removeChild', this._afterGuideStepRemove); | |
| this.after('selectionChange', this._afterGuideStepSelectionChange); | |
| this.on(GUIDE_STEP+':contentUpdate', this._onGuideStepContentUpdate); | |
| if (nextNode) { | |
| nextNode.on('click', Y.bind(this._onNextClick, this)); | |
| } | |
| if (closeNode) { | |
| closeNode.on('click', Y.bind(this._onCloseClick, this)); | |
| } | |
| if (nav && navNode) { | |
| navNode.delegate('click', Y.bind(this._onNavigationStepClick, this), DOT+this.getClassName(NAVIGATION, STEP)+' a'); | |
| } | |
| }, | |
| syncUI : function () { | |
| // adding stuff to WidgetStdMod content areas. | |
| if ( ! this.get('rendered')) { | |
| this._insertGuideStdModContent(); | |
| } | |
| // select the first GuideStep unless otherwise set. | |
| if ( ! this.get('selection')) { | |
| this.selectChild(0); | |
| } | |
| }, | |
| // *** Public Methods *** // | |
| // *** Private Methods *** // | |
| _insertGuideStdModContent : function () { | |
| var bodyContent = this.getStdModNode(STD_BODY), | |
| footerContent = this.getStdModNode(STD_FOOTER), | |
| stepsNode = this.get(STEPS_NODE), | |
| navNode = this.get(NAVIGATION_NODE), | |
| nextNode = this.get(NEXT_NODE), | |
| closeNode = this.get(CLOSE_NODE); | |
| // Prepend navigation to bodyContent (if it’s not already) | |
| if (this.get(NAVIGATION) && navNode && ! bodyContent.contains(navNode)) { | |
| this.setStdModContent(STD_BODY, navNode, 'before'); | |
| } | |
| // Append steps container to bodyContent (if it’s not already) | |
| if ( ! bodyContent.contains(stepsNode)) { | |
| this.setStdModContent(STD_BODY, stepsNode, 'after'); | |
| } | |
| // Append next button to footerContent (if it’s not already) | |
| if (nextNode && ! footerContent.contains(nextNode)) { | |
| this.setStdModContent(STD_FOOTER, nextNode, 'after'); | |
| } | |
| // Append close button to footerContent (if it’s not already) | |
| if (closeNode && ! footerContent.contains(closeNode)) { | |
| this.setStdModContent(STD_FOOTER, closeNode, 'after'); | |
| } | |
| }, | |
| _defStepsNodeValueFn : function () { | |
| return Y.Node.create(this.STEPS_TEMPLATE); | |
| }, | |
| _setStepsNode : function (node) { | |
| node = Y.one(node); | |
| if (node) { | |
| node.addClass(this.getClassName(STEPS)); | |
| } | |
| return node; | |
| }, | |
| _defNextNodeValueFn : function () { | |
| return Y.Node.create(this.NEXT_TEMPLATE).set('text', this.getString('defNextLabel')); | |
| }, | |
| _setNextNode : function (node) { | |
| node = Y.one(node); | |
| if (node) { | |
| node.addClass(this.getClassName(NEXT)); | |
| } | |
| return node; | |
| }, | |
| _defCloseNodeValueFn : function () { | |
| return Y.Node.create(this.CLOSE_TEMPLATE).set('text', this.getString('defCloseLabel')); | |
| }, | |
| _setCloseNode : function (node) { | |
| node = Y.one(node); | |
| if (node) { | |
| node.addClass(this.getClassName(CLOSE)); | |
| } | |
| return node; | |
| }, | |
| _defNavigationNodeValueFn : function () { | |
| return ( this.get(NAVIGATION) ? Y.Node.create(this.NAVIGATION_TEMPLATE) : null ); | |
| }, | |
| _setNavigationNode : function (node) { | |
| node = Y.one(node); | |
| if (node) { | |
| node.addClass(this.getClassName(NAVIGATION)); | |
| } | |
| return node; | |
| }, | |
| _uiUpdateNavigation : function () { | |
| var nav = this.get(NAVIGATION), | |
| navNode = this.get(NAVIGATION_NODE), | |
| selection = this.get('selection'), | |
| navStepClass = this.getClassName(NAVIGATION, STEP), | |
| navStepSelectedClass = this.getClassName(NAVIGATION, STEP, SELECTED); | |
| // we need both | |
| if ( ! (nav && navNode)) { return; } | |
| // clear out all the nav items | |
| navNode.all(DOT+navStepClass).remove(); | |
| // add navigation items for each GuideStep | |
| this.each(Y.bind(function(step){ | |
| var navItemNode = Y.Node.create(this.NAVIGATION_ITEM_TEMPLATE); | |
| navItemNode | |
| .addClass(navStepClass) | |
| .one('a') | |
| .set('href', '#' + step.get('id')) | |
| .set('text', step.get('label')); | |
| if (step === selection) { | |
| navItemNode.addClass(navStepSelectedClass); | |
| } | |
| navNode.append(navItemNode); | |
| }, this)); | |
| }, | |
| _uiUpdateNavigationSelected : function (prevStep, newStep) { | |
| var nav = this.get(NAVIGATION), | |
| navNode = this.get(NAVIGATION_NODE), | |
| navStepClass = this.getClassName(NAVIGATION, STEP), | |
| navStepSelectedClass = this.getClassName(NAVIGATION, STEP, SELECTED), | |
| navItemNodes; | |
| // we need both | |
| if ( ! (nav && navNode)) { return; } | |
| navItemNodes = navNode.all(DOT+navStepClass); | |
| // remove selected class from prev step | |
| if (prevStep) { | |
| navItemNodes.item(this.indexOf(prevStep)).removeClass(navStepSelectedClass); | |
| } | |
| // add selected class to newly selected step | |
| navItemNodes.item(this.indexOf(newStep)).addClass(navStepSelectedClass); | |
| }, | |
| _transitionGuide : function (callback) { | |
| var boundingBox = this.get(BOUNDING_BOX); | |
| // Smooooth transition between steps | |
| if (this.get('rendered')) { | |
| boundingBox.hide('fadeOut', { duration: 0.2 }, Y.bind(function(){ | |
| callback.call(this); | |
| boundingBox.show('fadeIn', { duration: 0.2 }); | |
| }, this)); | |
| } else { | |
| callback.call(this); | |
| } | |
| }, | |
| _uiSetGuideStep : function (prevStep, newStep) { | |
| // Hide all steps, but the old one so we can transition | |
| this.each(function(step){ | |
| if ( ! prevStep || step !== prevStep) { | |
| step.hide(); | |
| } | |
| }); | |
| this._transitionGuide(function(){ | |
| var align = newStep.get('align'), | |
| isLastStep; | |
| // hide old show newly selected step | |
| if (prevStep) { | |
| prevStep.hide(); | |
| } | |
| newStep.show(); | |
| // proxy selected steps alignment, or center Guide | |
| if (align && align.points) { | |
| this.align(align.node, align.points); | |
| } else { | |
| this.centered(); | |
| } | |
| // check and set next button to 'Done' if we need to | |
| if ( ! this.get(CIRCULAR)) { | |
| isLastStep = this.indexOf(newStep) === this.size() - 1; | |
| this.get(NEXT_NODE).setContent(isLastStep ? this.getString('defDoneLabel') : this._initialNextNodeContent); | |
| } | |
| // update navigation | |
| this._uiUpdateNavigationSelected(prevStep, newStep); | |
| }); | |
| }, | |
| _afterGuideStepAdd : function (e) { | |
| this._uiUpdateNavigation(); | |
| }, | |
| _afterGuideStepRemove : function (e) { | |
| this._uiUpdateNavigation(); | |
| }, | |
| _afterGuideStepSelectionChange : function (e) { | |
| this._uiSetGuideStep(e.prevVal, e.newVal); | |
| }, | |
| _onGuideStepContentUpdate : function (e) { | |
| if (e.target === this.get('selection')) { | |
| this.syncUI(); | |
| } | |
| }, | |
| _onNextClick : function (e) { | |
| var nextStep = this.get('selection').next(this.get(CIRCULAR)); | |
| if (nextStep) { | |
| this.selectChild(nextStep.get('index')); | |
| } else { | |
| this.hide(); | |
| } | |
| }, | |
| _onCloseClick : function (e) { | |
| this.hide(); | |
| }, | |
| _onNavigationStepClick : function (e) { | |
| var navStepSelector = DOT+this.getClassName(NAVIGATION, STEP), | |
| navSteps = this.get(NAVIGATION_NODE).all(navStepSelector); | |
| this.selectChild(navSteps.indexOf(e.currentTarget.ancestor(navStepSelector))); | |
| e.preventDefault(); | |
| } | |
| }, { | |
| // *** Static *** // | |
| ATTRS : { | |
| strings : { | |
| value : { | |
| defNextLabel : 'Next', | |
| defDoneLabel : 'Done', | |
| defCloseLabel : 'Close' | |
| } | |
| }, | |
| // Override WidgetParent value | |
| defaultChildType : { | |
| value : GuideStep | |
| }, | |
| // Override WidgetParent value and readOnly | |
| multiple : { | |
| value : false, | |
| readOnly : true | |
| }, | |
| /** | |
| * @attribute circular | |
| * @type {Boolean} | |
| * @default false | |
| * @description Whether or not the next button changes to a done button when on the last step. | |
| */ | |
| circular : { | |
| value : false, | |
| validator : isBoolean | |
| }, | |
| /** | |
| * @attribute stepsNode | |
| * @type {Node} | |
| * @default node | |
| * @description The children container Node where the GuideSteps are rendered into. | |
| */ | |
| stepsNode : { | |
| valueFn : '_defStepsNodeValueFn', | |
| setter : '_setStepsNode', | |
| writeOnce : true | |
| }, | |
| /** | |
| * @attribute nextNode | |
| * @type {Node} | |
| * @default node | |
| * @description The button to move to the next GuideStep. | |
| */ | |
| nextNode : { | |
| valueFn : '_defNextNodeValueFn', | |
| setter : '_setNextNode', | |
| writeOnce : true | |
| }, | |
| /** | |
| * @attribute closeNode | |
| * @type {Node} | |
| * @default node | |
| * @description The button to close the Guide. | |
| */ | |
| closeNode : { | |
| valueFn : '_defCloseNodeValueFn', | |
| setter : '_setCloseNode', | |
| writeOnce : true | |
| }, | |
| /** | |
| * @attribute navigation | |
| * @type {Boolean} | |
| * @default true | |
| * @description Whether or not to render a navigation list for the Guide’s Steps. | |
| */ | |
| navigation : { | |
| value : true, | |
| validator : isBoolean, | |
| initOnly : true | |
| }, | |
| /** | |
| * @attibute navigationNode | |
| * @type {Node} | |
| * @default node | |
| * @description The Node for the navigation for the GuideSteps as a list of the GuideStep labels | |
| */ | |
| navigationNode : { | |
| valueFn : '_defNavigationNodeValueFn', | |
| setter : '_setNavigationNode', | |
| writeOnce : true | |
| } | |
| }, | |
| HTML_PARSER : { | |
| stepsNode : DOT+getCN(GUIDE, STEPS), | |
| nextNode : DOT+getCN(GUIDE, NEXT), | |
| closeNode : DOT+getCN(GUIDE, CLOSE), | |
| navigationNode : DOT+getCN(GUIDE, NAVIGATION) | |
| } | |
| }); | |
| // *** Namespace *** // | |
| Y.namespace('TTW'); | |
| Y.TTW.Guide = Guide; | |
| Y.TTW.GuideStep = GuideStep; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment