Reusable Accessible Mapping Platform

API Docs for: 5.3.1
Show:

File: src/js/RAMP/Modules/stepItem.js

  1. /* global define, $, TimelineLite */
  2.  
  3. /**
  4. * @module RAMP
  5. * @submodule FilterManager
  6. * @main FilterManager
  7. */
  8.  
  9. /**
  10. * Creates a step in the choice tree. A step item can contain several bricks in it and can take different states. Each step can advance and retreat by either displaying its selected child or hiding it.
  11. *
  12. * ####Imports RAMP Modules:
  13. * {{#crossLink "Util"}}{{/crossLink}}
  14. * {{#crossLink "TmplHelper"}}{{/crossLink}}
  15. * {{#crossLink "TmplUtil"}}{{/crossLink}}
  16. * {{#crossLink "Array"}}{{/crossLink}}
  17. * {{#crossLink "Dictionary"}}{{/crossLink}}
  18. * {{#crossLink "Bricks"}}{{/crossLink}}
  19. *
  20. *
  21. * ####Uses RAMP Templates:
  22. * {{#crossLink "templates/layer_selector_template.json"}}{{/crossLink}}
  23. *
  24. * @class StepItem
  25. * @constructor
  26. * @uses dojo/Evented
  27. * @uses dojo/_base/declare
  28. * @uses dojo/lang
  29. * @uses dojo/Deferred
  30. *
  31. * @param {Object} config a config definition of the step item
  32. * @param {String} config.id step item it; can be anything
  33. * @param {Number} config.level level of this step item
  34. * @param {Array} config.content an array of Brick configuration objects
  35. * @param {String} config.content.[].id content brick id
  36. * @param {Brick} config.content.[].type type of the content brick
  37. * @param {Object} config.content.[].config a brick config object that will be passed to Brick.new() init function
  38. * @param {Array} config.content.[].on a set of callbacks set on the create Brick object
  39. * @param {String} config.content.[].on.[].eventName a name of the Brick event the callback should react to
  40. * @param {Function} config.content.[].on.[].callback a function to be executed when the specified event is fired
  41. *
  42. * @return {StepItem} A built StepItem object.
  43. */
  44.  
  45. define([
  46. "dojo/Evented", "dojo/_base/declare", "dojo/_base/lang", "dojo/Deferred",
  47.  
  48. /* Text */
  49. "dojo/text!./templates/filter_manager_template.json",
  50.  
  51. /* Util */
  52. "utils/util", "utils/tmplHelper", "utils/tmplUtil", "utils/array", "utils/dictionary", "utils/bricks"
  53. ],
  54. function (
  55. Evented, declare, lang, Deferred,
  56.  
  57. /* Text */
  58. filter_manager_template,
  59.  
  60. /* Util */
  61. UtilMisc, TmplHelper, TmplUtil, UtilArray, UtilDict, Bricks
  62. ) {
  63. "use strict";
  64.  
  65. var StepItem,
  66. ALL_STATES_CLASS,
  67.  
  68. templates = JSON.parse(TmplHelper.stringifyTemplate(filter_manager_template));
  69.  
  70. StepItem = declare([Evented], {
  71. constructor: function (config) {
  72. var that = this;
  73.  
  74. // declare individual properties inside the constructor: http://dojotoolkit.org/reference-guide/1.9/dojo/_base/declare.html#id6
  75. lang.mixin(this,
  76. {
  77. /**
  78. * Layer id. Upon initialization, `id` can be overwritten by `config.id` value.
  79. *
  80. * @property id
  81. * @type String
  82. * @default null
  83. */
  84. id: null,
  85.  
  86. /**
  87. * Indicates the level of the step, or how far down the tree this step appears.
  88. *
  89. * @property level
  90. * @type Number
  91. * @default 0
  92. *
  93. */
  94. level: 0,
  95.  
  96. /**
  97. * A node of the StepItem.
  98. *
  99. * @property node
  100. * @type JObject
  101. * @default null
  102. */
  103. node: null,
  104.  
  105. /**
  106. * An array of Brick configs and other related properties.
  107. *
  108. * @property content
  109. * @type {Array}
  110. * @default null
  111. * @private
  112. * @example
  113. * [{
  114. * id: "sourceType",
  115. * type: Bricks.ChoiceBrick,
  116. * config: {
  117. * header: i18n.t("addDataset.dataSource"),
  118. * instructions: i18n.t("addDataset.help.dataSource"),
  119. * choices: [
  120. * {
  121. * key: "serviceTypeStep",
  122. * value: i18n.t("addDataset.dataSourceService")
  123. * },
  124. * {
  125. * key: "fileTypeStep",
  126. * value: i18n.t("addDataset.dataSourceFile")
  127. * }
  128. * ]
  129. * },
  130. * on: [
  131. * {
  132. * eventName: Bricks.ChoiceBrick.event.CHANGE,
  133. * //expose: { as: "advance" },
  134. * callback: choiceTreeCallbacks.simpleAdvance
  135. * }
  136. * ]
  137. * }]
  138. *
  139. */
  140. content: null,
  141.  
  142. /**
  143. * A collection of build Bricks that can accessed by their ids.
  144. *
  145. * @property contentBricks
  146. * @type {Object}
  147. * @default {}
  148. */
  149. contentBricks: {},
  150.  
  151. /**
  152. * Default template used for building step items.
  153. *
  154. * @property template
  155. * @type {String}
  156. * @default "default_step_template"
  157. */
  158. template: "default_step_template",
  159.  
  160. /**
  161. * Node of the content div.
  162. *
  163. * @private
  164. * @property _contentNode
  165. * @default null
  166. */
  167. _contentNode: null,
  168. /**
  169. * Node of the options container.
  170. *
  171. * @private
  172. * @property _optionsContainerNode
  173. * @default null
  174. */
  175. _optionsContainerNode: null,
  176. /**
  177. * Node of the options background node. It's used to change the state of the child steps - SUCCESS, ERROR, etc.
  178. *
  179. * @private
  180. * @property _optionsBackgroundNode
  181. * @default null
  182. */
  183. _optionsBackgroundNode: null,
  184. /**
  185. * Node of the options div.
  186. *
  187. * @private
  188. * @property _optionsNode
  189. * @default null
  190. */
  191. _optionsNode: null,
  192.  
  193. /**
  194. * A collection of the child step items of this step item. Should not be accessed directly.
  195. *
  196. * @private
  197. * @property _childSteps
  198. * @default {}
  199. */
  200. _childSteps: {},
  201.  
  202. /**
  203. * A step item of the currently active child of this step item. If there is no active child, it means that a choice or action hasn't been made on this step yet or it's the last step in the branch.
  204. *
  205. * @private
  206. * @property _activeChildStep
  207. * @default null
  208. */
  209. _activeChildStep: null,
  210.  
  211. /**
  212. * A step item of the parent step item if any. Used only for animating background when opening/collapsing (error) notices.
  213. *
  214. * @private
  215. * @property _parent
  216. * @default null
  217. */
  218. _parent: null,
  219.  
  220. /**
  221. * An object containing some data. This is used like that: when the step is advanced, a data object is provided by external code; this object is then passed to whichever child is being advanced so it can be retrieved later without external code having to store it somewhere.
  222. *
  223. * @private
  224. * @property _stepData
  225. * @default null
  226. */
  227. _stepData: {},
  228.  
  229. /**
  230. * The current state of this step item.
  231. *
  232. * @private
  233. * @property _state
  234. * @default StepItem.state.DEFAULT,
  235. */
  236. _state: StepItem.state.DEFAULT,
  237.  
  238. /**
  239. * A timeline of this step item. Used for animation
  240. *
  241. * @private
  242. * @property _timeline
  243. */
  244. _timeline: new TimelineLite({ paused: true }),
  245. /**
  246. * A default duration value for all single transitions of any elements of this step.
  247. *
  248. * @private
  249. * @property _transitionDuration
  250. * @default 0.4
  251. */
  252. _transitionDuration: 0.4
  253. },
  254. config
  255. );
  256.  
  257. this.node = $(TmplHelper.template.call(null, this.template, config, templates));
  258.  
  259. this._contentNode = this.node.find("> .step-content");
  260. this._optionsContainerNode = this.node.find("> .step-options-container");
  261. this._optionsBackgroundNode = this._optionsContainerNode.find("> .options-bg");
  262. this._optionsNode = this._optionsContainerNode.find("> .step-options");
  263.  
  264. this.content.forEach(function (contentItem) {
  265. that._addContentBrick(contentItem);
  266. });
  267.  
  268. // console.debug("-->", this._state);
  269. },
  270.  
  271. /**
  272. * Instantiates and adds a new brick to this step item.
  273. *
  274. * @method _addContentBrick
  275. * @param {Object} contentItem a config object for a Brick
  276. * @param {Object} contentItem.id Brick id
  277. * @param {Object} contentItem.config actual Brick config
  278. * @private
  279. */
  280. _addContentBrick: function (contentItem) {
  281. var that = this,
  282. contentBrick = contentItem.type.new(contentItem.id, contentItem.config);
  283.  
  284. // if it's a multiBrick, add individual bricks from its content to the main content and wire them as separate bricks
  285. if (Bricks.MultiBrick === contentItem.type) {
  286.  
  287. contentBrick.content.forEach(function (contentItem) {
  288. that._wireBrickUp(contentItem, contentBrick.contentBricks[contentItem.id]);
  289. });
  290.  
  291. } else {
  292. that._wireBrickUp(contentItem, contentBrick);
  293. }
  294.  
  295. this._contentNode.append(contentBrick.node);
  296.  
  297. this._doInternalCheck();
  298. },
  299.  
  300. /**
  301. * Wire up listeners on the given Brick.
  302. *
  303. * @method _wireBrickUp
  304. * @param {Object} contentItem a config object for a Brick
  305. * @param {Object} contentBrick an actual Brick instance
  306. * @private
  307. */
  308. _wireBrickUp: function (contentItem, contentBrick) {
  309. var that = this;
  310. this.contentBricks[contentBrick.id] = contentBrick;
  311.  
  312. // set brick events if specified
  313. if (contentItem.on) {
  314. contentItem.on.forEach(function (o) {
  315. contentBrick.on(o.eventName, function (data) {
  316. // if there is a callback specified, call it in the context of the brick
  317. if (o.callback) {
  318. o.callback.call(contentBrick, that, data);
  319. }
  320.  
  321. // if event is exposed; emit it
  322. if (o.expose) {
  323. that._doInternalCheck();
  324. that.emit(contentBrick.id + "/" + o.eventName, data);
  325.  
  326. if (o.expose.as) {
  327. that.emit(o.expose.as, {
  328. brick: contentBrick,
  329. brickData: data
  330. });
  331. }
  332. }
  333. });
  334. });
  335. }
  336.  
  337. // do a check of all the bricks in case some of them depend on validity of other bricks in this step
  338. contentBrick.on(Bricks.Brick.event.CHANGE, function () {
  339. that._doInternalCheck();
  340. });
  341. },
  342.  
  343. /**
  344. * Checks Brick's requirements. Enables or disabled the target Brick based on validity of its requirements.
  345. *
  346. * @method _internalCheckHelper
  347. * @param {Array} required an array of required rules
  348. * @param {Brick} targetBrick a Brick with requirements
  349. * @param {Object} contentBricks a dictionary of bricks available in this step
  350. * @private
  351. */
  352. _internalCheckHelper: function (required, targetBrick, contentBricks) {
  353. var flag = false;
  354.  
  355. switch (required.type) {
  356. case "all":
  357. flag = required.check.every(function (ch) {
  358. return contentBricks[ch].isValid();
  359. });
  360. break;
  361.  
  362. case "any":
  363. flag = required.check.some(function (ch) {
  364. return contentBricks[ch].isValid();
  365. });
  366. break;
  367. }
  368.  
  369. // disable or enable a brick based on sum validity of its dependencies
  370. targetBrick.disable(!flag);
  371. },
  372.  
  373. /**
  374. * Checks this step item validity by checking validity of all its Bricks.
  375. *
  376. * @method _doInternalCheck
  377. * @private
  378. */
  379. _doInternalCheck: function () {
  380. var that = this;
  381.  
  382. UtilDict.forEachEntry(this.contentBricks, function (key, brick) {
  383.  
  384. if (brick.required) {
  385.  
  386. // if it's a MultiBrick, check requirements for each of the Bricks in MultiBrick
  387. if (Bricks.MultiBrick.isPrototypeOf(brick)) {
  388.  
  389. if (Array.isArray(brick.required)) {
  390.  
  391. brick.required.forEach(function (req) {
  392. that._internalCheckHelper(req, brick.contentBricks[req.id], that.contentBricks);
  393. });
  394.  
  395. } else {
  396. that._internalCheckHelper(brick.required, brick, that.contentBricks);
  397. }
  398.  
  399. } else {
  400. that._internalCheckHelper(brick.required, brick, that.contentBricks);
  401. }
  402. }
  403. });
  404.  
  405. // if the step in the error state and one of the Bricks is changed, switched to the DEFAULT state and switch all the content Bricks
  406. if (this._state === StepItem.state.ERROR) {
  407. this._notifyStateChange(StepItem.state.DEFAULT);
  408. }
  409. },
  410.  
  411. /**
  412. * Creates timeline for retreat animation - when the part of the choice tree is collapsing, switching to another branch of the tree.
  413. *
  414. * @method _makeCloseTimeline
  415. * @param {Boolean} skipFirst indicates whether the first child step should be included in the timeline
  416. * @param {Boolean} resetState indicates if the child step state should be reset
  417. * @return {Object} a constructed close timeline
  418. * @private
  419. */
  420. _makeCloseTimeline: function (skipFirst, resetState) {
  421. var closeTimeline = new TimelineLite(),
  422. closeStagger,
  423. closeTimelines = [];
  424.  
  425. this._getCloseTimelines(closeTimelines, skipFirst, resetState);
  426. closeTimelines = closeTimelines.reverse();
  427.  
  428. if (closeTimelines.length > 0) {
  429. closeStagger = this._transitionDuration / 2 / closeTimelines.length;
  430. closeTimeline.add(closeTimelines, "+=0", "start", closeStagger);
  431. }
  432.  
  433. return closeTimeline;
  434. },
  435.  
  436. /**
  437. * Generates a close timeline for this particular step item and adds it to the global close timeline. Calls the same on the target child.
  438. *
  439. * @method _getCloseTimelines
  440. * @param {Object} tls global close timeline
  441. * @param {Boolean} skip indicates whether to skip the first child step item
  442. * @param {Boolean} reset indicates whether to reset the step item state to DEFAULT
  443. * @return {StepItem} itself
  444. * @private
  445. * @chainable
  446. */
  447. _getCloseTimelines: function (tls, skip, reset) {
  448. var tl = new TimelineLite(),
  449.  
  450. that = this;
  451.  
  452. if (this._activeChildStep) {
  453.  
  454. if (!skip) {
  455. tl
  456. .call(function () {
  457. //that.currentLevel()
  458. that._notifyCurrentStepChange();
  459. })
  460. .to(this._optionsContainerNode, this._transitionDuration,
  461. { top: -this._activeChildStep.getContentOuterHeight(), ease: "easeOutCirc" },
  462. 0)
  463. .set(this._activeChildStep, { className: "-=active-option" })
  464. .set(this._optionsContainerNode, { display: "none" })
  465. ;
  466.  
  467. tls.push(tl);
  468. }
  469.  
  470. if (reset) {
  471. this._notifyStateChange(StepItem.state.DEFAULT);
  472. }
  473.  
  474. this._activeChildStep._getCloseTimelines(tls);
  475. }
  476.  
  477. return this;
  478. },
  479.  
  480. /**
  481. * Creates timeline for shift animation - when the selected option for a choice is changing - animating horizontally.
  482. *
  483. * @method _makeShiftTimeline
  484. * @param {String} targetChildStepId specifies the target childId
  485. * @return {Object} a constructed shift timeline
  486. * @private
  487. */
  488. _makeShiftTimeline: function (targetChildStepId) {
  489. var shiftTimeline = new TimelineLite(),
  490. targetChildStep = this._childSteps[targetChildStepId],
  491. allChildNodes = this._getChildNodes(),
  492. otherChildNodes = this._getChildNodes([targetChildStepId]);
  493.  
  494. if (this._activeChildStep) {
  495.  
  496. shiftTimeline
  497. .set(allChildNodes, { display: "inline-block" })
  498.  
  499. .to(this._optionsBackgroundNode, this._transitionDuration, {
  500. height: targetChildStep.getContentOuterHeight(),
  501. "line-height": targetChildStep.getContentOuterHeight(),
  502. ease: "easeOutCirc"
  503. }, 0)
  504.  
  505. .fromTo(this._optionsNode, this._transitionDuration,
  506. { left: -this._activeChildStep.getContentPosition().left },
  507. { left: -targetChildStep.getContentPosition().left, ease: "easeOutCirc" }, 0)
  508. .set(otherChildNodes, { className: "-=active-option" }) // when shifting, active-option is changing
  509. .set(targetChildStep.node, { className: "+=active-option" })
  510.  
  511. .set(this._optionsNode, { left: 0 })
  512. .set(otherChildNodes, { display: "none" })
  513. .call(function () {
  514. targetChildStep._notifyStateChange(targetChildStep._state);
  515. }, null, null, this._transitionDuration / 3)
  516. ;
  517. }
  518.  
  519. return shiftTimeline;
  520. },
  521.  
  522. /**
  523. * Creates timeline for advance animation - when the part of the choice tree is unfolding, (after switching to another branch of the tree).
  524. *
  525. * @method _makeOpenTimeline
  526. * @param {String} targetChildStepId specifies the target child id
  527. * @param {Boolean} skipFirst indicates whether the first child step should be included in the timeline
  528. * @return {Object} a constructed open timeline
  529. * @private
  530. */
  531. _makeOpenTimeline: function (targetChildStepId, skipFirst) {
  532. var openTimeline = new TimelineLite(),
  533. openStagger,
  534. openTimelines = [];
  535.  
  536. this._getOpenTimelines(openTimelines, targetChildStepId, skipFirst);
  537.  
  538. if (openTimelines.length > 0) {
  539. openStagger = this._transitionDuration / 2 / openTimelines.length;
  540. openTimeline.add(openTimelines, "+=0", "start", openStagger);
  541. }
  542.  
  543. return openTimeline;
  544. },
  545.  
  546. /**
  547. * Generates an open timeline for this particular step item and adds it to the global open timeline. Calls the same on the target child.
  548. *
  549. * @method _getOpenTimelines
  550. * @param {Object} tls global open timeline
  551. * @param {String} targetChildStepId specifies the target child id
  552. * @param {Boolean} skip indicates whether to skip the first child step item
  553. * @return {StepItem} itself
  554. * @private
  555. * @chainable
  556. */
  557. _getOpenTimelines: function (tls, targetChildStepId, skip) {
  558. var tl = new TimelineLite(),
  559. targetChildStep = targetChildStepId ? this._childSteps[targetChildStepId] : this._activeChildStep,
  560. otherChildNodes = this._getChildNodes([targetChildStepId]);
  561.  
  562. if (targetChildStep) {
  563.  
  564. if (!skip) {
  565.  
  566. tl
  567. // set options container node to visible, otherwise you can't get its size
  568. .set(this._optionsContainerNode, { display: "block", top: -9999 }, 0)
  569.  
  570. // make sure options' node is on the left
  571. .set(this._optionsNode, { left: 0 }, 0)
  572.  
  573. // hide children other than target
  574. .set(otherChildNodes, { display: "none" }, 0)
  575. .set(targetChildStep.node, { className: "+=active-option", display: "inline-block" }, 0)
  576.  
  577. // make the target step current
  578. .call(function () {
  579. targetChildStep._notifyCurrentStepChange();
  580. })
  581.  
  582. // animate step's background
  583. .to(this._optionsBackgroundNode, 0, {
  584. height: targetChildStep.getContentOuterHeight(),
  585. "line-height": targetChildStep.getContentOuterHeight()
  586. }, 0)
  587.  
  588. // animate height and position of the options' container node
  589. .to(this._optionsContainerNode, 0, { height: targetChildStep.getContentOuterHeight(), ease: "easeOutCirc" }, 0)
  590. .fromTo(this._optionsContainerNode, this._transitionDuration,
  591. { top: -this._optionsContainerNode.height() },
  592. { top: 0, ease: "easeOutCirc" },
  593. 0)
  594. .set(this._optionsContainerNode, { height: "auto" })
  595. ;
  596.  
  597. tls.push(tl);
  598. }
  599.  
  600. this._notifyStateChange(StepItem.state.SUCCESS);
  601. // hide all notices when making a step successful
  602. this.displayBrickNotices();
  603. targetChildStep._getOpenTimelines(tls);
  604. }
  605.  
  606. return this;
  607. },
  608.  
  609. /**
  610. * Returns an array of child step nodes except for steps whose ids are passed in `except` param.
  611. *
  612. * @method _getChildNodes
  613. * @private
  614. * @param {Array} except an array of child step ids to not include in the result
  615. * @return {Array} an array of child step nodes
  616. */
  617. _getChildNodes: function (except) {
  618. var childNodes = [];
  619.  
  620. UtilDict.forEachEntry(this._childSteps,
  621. function (childId, childItem) {
  622. if (!except || except.indexOf(childItem.id) === -1) {
  623. childNodes.push(childItem.node);
  624. }
  625. }
  626. );
  627.  
  628. return childNodes;
  629. },
  630.  
  631. /**
  632. * Emits a `CURRENT_STEP_CHANGE` event with a payload of id and level of the current step item.
  633. * This notifies the trunk of the tree and this step is now a current step. The trunk in turn notifies
  634. * every other step that they are not current steps.
  635. *
  636. * @method _notifyCurrentStepChange
  637. * @private
  638. */
  639. _notifyCurrentStepChange: function () {
  640. this._emit(StepItem.event.CURRENT_STEP_CHANGE, { id: this.id, level: this.level });
  641. },
  642.  
  643. /**
  644. * Emits a `STATE_CHANGE` event with a payload of id, level and state of the current step item.
  645. * Additionally sets state of all the content bricks to corresponding states.
  646. *
  647. * @method _notifyStateChange
  648. * @private
  649. * @chainable
  650. * @param {String} state state to set the step item to
  651. * @return {StepItem} itself
  652. */
  653. _notifyStateChange: function (state) {
  654. var brickState;
  655.  
  656. this._state = state;
  657.  
  658. switch (state) {
  659. case StepItem.state.SUCCESS:
  660. brickState = Bricks.Brick.state.SUCCESS;
  661. break;
  662. case StepItem.state.ERROR:
  663. brickState = Bricks.Brick.state.ERROR;
  664. break;
  665. default:
  666. brickState = Bricks.Brick.state.DEFAULT;
  667. break;
  668. }
  669.  
  670. UtilDict.forEachEntry(this.contentBricks, function (key, brick) {
  671. brick.setState(brickState);
  672. });
  673.  
  674. this._emit(StepItem.event.STATE_CHANGE, { id: this.id, level: this.level, state: this._state });
  675.  
  676. return this;
  677. },
  678.  
  679. /**
  680. * A helper function to emit a supplied event with payload.
  681. *
  682. * @private
  683. * @chainable
  684. * @param {String} event event name
  685. * @param {Object} [payload] payload object
  686. * @return {StepItem} itself
  687. */
  688. _emit: function (event, payload) {
  689. this.emit(event, payload);
  690.  
  691. return this;
  692. },
  693.  
  694. /**
  695. * Returns step data and data from all content bricks.
  696. *
  697. * @return {Object} step data and brick data
  698. */
  699. getData: function () {
  700. var data = {
  701. stepData: this._stepData,
  702. bricksData: {}
  703. };
  704.  
  705. UtilDict.forEachEntry(this.contentBricks, function (key, brick) {
  706. lang.mixin(data.bricksData, brick.getData(true));
  707. });
  708.  
  709. return data;
  710. },
  711.  
  712. /**
  713. * Adds a given step item object as a child for this step item.
  714. *
  715. * @method addChild
  716. * @chainable
  717. * @param {StepItem} stepItem a stepItem object to be added as a child.
  718. * @return {StepItem} itself
  719. */
  720. addChild: function (stepItem) {
  721. this._optionsNode.append(stepItem.node);
  722. this._childSteps[stepItem.id] = stepItem;
  723. stepItem._parent = this;
  724.  
  725. return this;
  726. },
  727.  
  728. /**
  729. * Clears this step by resetting its state to `DEFAULT`, clearing all content bricks, and hide all brick notices.
  730. *
  731. * @method
  732. * @param {Array} brickIds [description]
  733. * @return {StepItem} itself
  734. * @chainable
  735. */
  736. clearStep: function (brickIds) {
  737. var bricks = []; // bricks from whose notices should be hidden
  738.  
  739. // clear this steps state
  740. this._notifyStateChange(StepItem.state.DEFAULT);
  741.  
  742. if (Array.isArray(brickIds)) {
  743. brickIds.forEach(function (brickId) {
  744. this.contentBricks[brickId].clear();
  745.  
  746. bricks.push(this.contentBricks[brickId]);
  747. });
  748. } else {
  749. UtilDict.forEachEntry(this.contentBricks, function (key, brick) {
  750. brick.clear();
  751.  
  752. bricks.push(brick);
  753. });
  754. }
  755.  
  756. // hide all notices when clearing the step
  757. this.displayBrickNotices();
  758.  
  759. return this;
  760. },
  761.  
  762. /**
  763. * Sets the step specified by the `level` and `stepId` to a specified state.
  764. *
  765. * @method setState
  766. * @param {Number} level level of the step to set the state on
  767. * @param {String} stepId id of the step to set the state on
  768. * @param {String} state name of the state to set
  769. */
  770. setState: function (level, stepId, state) {
  771. var that = this;
  772.  
  773. // if this step is the first step in the tree and so is the current step, set state class on its main node
  774. if (this.level === 1 && level === 1) {
  775. this.node
  776. .removeClass(ALL_STATES_CLASS)
  777. .addClass(state);
  778. } else {
  779. // if not, go over the children and if one corresponds to the current step, set state class on the options (children) container
  780. UtilDict.forEachEntry(this._childSteps,
  781. function (childId, childStep) {
  782. if (childId === stepId && childStep.level === level) {
  783. that._optionsContainerNode
  784. .removeClass(ALL_STATES_CLASS)
  785. .addClass(state);
  786. }
  787. }
  788. );
  789. }
  790. },
  791.  
  792. /**
  793. * Makes the step specified by the `level` and `stepId` a current step by setting a proper CSS class.
  794. *
  795. * @method currentStep
  796. * @param {Number} level step level
  797. * @param {String} stepId step id
  798. */
  799. currentStep: function (level, stepId) {
  800. var that = this;
  801.  
  802. // if this step is the first step in the tree and so is the current step, set class on the main node of this step
  803. if (this.level === 1 && level === 1) {
  804. this.node.addClass(StepItem.currentStepClass);
  805. } else {
  806. this.node.removeClass(StepItem.currentStepClass);
  807. this._optionsContainerNode.removeClass(StepItem.currentStepClass);
  808.  
  809. // if not, go over the children and if one corresponds to the current step, set class on the options (children) container
  810. UtilDict.forEachEntry(this._childSteps,
  811. function (childId, childStep) {
  812. if (childId === stepId && childStep.level === level) {
  813. that._optionsContainerNode.addClass(StepItem.currentStepClass);
  814. }
  815. }
  816. );
  817. }
  818. },
  819.  
  820. /**
  821. * Checks if the step is valid. It's considered valid if all its content bricks are valid.
  822. *
  823. * @method isValid
  824. * @return {Boolean} true if completed; false, otherwise
  825. */
  826. isValid: function () {
  827. UtilDict.forEachEntry(this.contentBricks, function (key, brick) {
  828. if (!brick.isValid()) {
  829. return false;
  830. }
  831. });
  832.  
  833. return true;
  834. },
  835.  
  836. /**
  837. * Checks if the step is completed. It's considered completed if its state is SUCCESS.
  838. *
  839. * @method isCompleted
  840. * @return {Boolean} true if completed; false, otherwise
  841. */
  842. isCompleted: function () {
  843. return this._state === StepItem.state.SUCCESS;
  844. },
  845.  
  846. /**
  847. * Sets data to the content brick
  848. *
  849. * @method setData
  850. * @param {Object} data a data object
  851. * @param {Object} [data.bricksData] dictionary of data where keys are brick ids and values data to be passed to the corresponding bricks
  852. * @param {Object} [data.stepData] some data object to be saved in this step
  853. * @return {StepItem} itself
  854. * @chainable
  855. */
  856. setData: function (data) {
  857. var that = this;
  858.  
  859. if (data) {
  860. if (data.bricksData) {
  861. UtilDict.forEachEntry(data.bricksData, function (brickId, brickData) {
  862. that.contentBricks[brickId].setData(brickData);
  863. });
  864. }
  865.  
  866. if (data.stepData) {
  867. this._stepData = data.stepData;
  868. }
  869. }
  870. },
  871.  
  872. /**
  873. * Set Brick notices, mostly errors.
  874. *
  875. * @method displayBrickNotices
  876. * @param {Object} [data] a dictionary of objects containing Brick notices
  877. */
  878. displayBrickNotices: function (data) {
  879. var that = this,
  880. bricks = [],
  881. promise;
  882.  
  883. if (data) {
  884. UtilDict.forEachEntry(data, function (brickId, brickData) {
  885. that.contentBricks[brickId].displayNotice(brickData);
  886.  
  887. bricks.push(that.contentBricks[brickId]);
  888. });
  889.  
  890. // toggle notice
  891. this._toggleBrickNotices(bricks, data);
  892. } else {
  893. UtilDict.forEachEntry(this.contentBricks, function (key, brick) {
  894. bricks.push(brick);
  895. });
  896.  
  897. // if no data provided, first hide all existing notices, then empty them
  898. promise = this._toggleBrickNotices(bricks, data);
  899.  
  900. promise.then(function () {
  901. bricks.forEach(function (brick) {
  902. brick.displayNotice();
  903. });
  904. });
  905. }
  906. },
  907.  
  908. /**
  909. * Toggles the visibility of notices for specified bricks.
  910. *
  911. * @method _toggleBrickNotices
  912. * @private
  913. * @param {Array} bricks an array of Brick items to toggle notices on
  914. * @param {Boolean} show a flag indicating whether to show or hide the notices
  915. * @return {Promise} a promise that is resolved after animation is completed
  916. */
  917. _toggleBrickNotices: function (bricks, show) {
  918. var that = this,
  919. notices,
  920. contentHeight = this.getContentOuterHeight(),
  921. heightChange = 0,
  922. tl = new TimelineLite({ paused: true }),
  923. def = new Deferred();
  924.  
  925. tl.eventCallback("onComplete", function () {
  926. def.resolve();
  927. });
  928.  
  929. // filter out bricks that don't have any notices
  930. notices = bricks
  931. .map(function (brick) { return brick.noticeNode; })
  932. .filter(function (notice) { return notice.length > 0; })
  933. ;
  934.  
  935. if (show) {
  936. tl.set(notices, { height: 0, visibility: "visible", position: "relative" }, 0);
  937. }
  938.  
  939. // add notice animation to the timeline
  940. notices.forEach(function (notice) {
  941.  
  942. heightChange += notice.height();
  943.  
  944. tl
  945. .to(notice, that._transitionDuration / 2, { height: show ? notice.height() : 0, ease: "easeOutCirc" }, 0)
  946. ;
  947.  
  948. });
  949.  
  950. if (!show) {
  951. tl.set(notices, { clearProps: "all" });
  952. }
  953.  
  954. heightChange = show ? 0 : -heightChange;
  955.  
  956. // change the height of the parent's option background container to accommodate for notice height
  957. if (this._parent) {
  958. tl.to(this._parent._optionsBackgroundNode, this._transitionDuration / 2, {
  959. height: contentHeight + heightChange,
  960. "line-height": contentHeight + heightChange,
  961. ease: "easeOutCirc"
  962. }, 0);
  963. }
  964.  
  965. tl.play();
  966.  
  967. return def.promise;
  968. },
  969.  
  970. /**
  971. * Retreats the current step item by collapsing its active children and resetting their states to default. After, active child step is set to null.
  972. *
  973. * @method retreat
  974. * @return {StepItem} itself
  975. * @chainable
  976. */
  977. retreat: function () {
  978. var closeTimeline,
  979. that = this;
  980.  
  981. this._timeline
  982. .seek("+=0", false)
  983. .clear()
  984. ;
  985.  
  986. closeTimeline = this._makeCloseTimeline(false, true);
  987.  
  988. this._timeline
  989. .add(closeTimeline)
  990. .call(function () {
  991. that._activeChildStep = null;
  992. })
  993. ;
  994.  
  995. this._timeline.play(0);
  996.  
  997. return this;
  998. },
  999.  
  1000. /**
  1001. * Advances the current step to the step with the provided id. The target id has to be a child step.
  1002. * Additionally, the tree expands down if the target child has an active child as well, and so on, until no active child is present.
  1003. *
  1004. * @method advance
  1005. * @param {String} targetChildStepId id of the new target step of advance too
  1006. * @param {Object} [targetChildData] data to be passed to the target step as it opens
  1007. * @return {StepItem} itself
  1008. */
  1009. advance: function (targetChildStepId, targetChildData) {
  1010. var closeTimeline,
  1011. shiftTimeline,
  1012. openTimeline,
  1013. targetChildStep = this._childSteps[targetChildStepId],
  1014. skipFirst,
  1015.  
  1016. that = this;
  1017.  
  1018. // cannot advance if the target is not specified
  1019. if (!targetChildStep) {
  1020. return this;
  1021. }
  1022.  
  1023. // reset timeline to the start and clear all the other rubbish that might be running already
  1024. this._timeline
  1025. .seek("+=0", false)
  1026. .clear()
  1027. ;
  1028.  
  1029. // if there is already an active child step, skip the first animation
  1030. skipFirst = this._activeChildStep ? true : false;
  1031.  
  1032. targetChildStep.setData(targetChildData);
  1033.  
  1034. closeTimeline = this._makeCloseTimeline(skipFirst);
  1035. shiftTimeline = this._makeShiftTimeline(targetChildStepId);
  1036. openTimeline = this._makeOpenTimeline(targetChildStepId, skipFirst);
  1037.  
  1038. this._timeline
  1039. .add(closeTimeline)
  1040. .add(shiftTimeline)
  1041. .add(openTimeline)
  1042. .call(function () {
  1043. // only when animation completes, set the active child to the target child
  1044. that._activeChildStep = targetChildStep;
  1045. })
  1046. ;
  1047.  
  1048. this._timeline.play(0);
  1049.  
  1050. return this;
  1051. },
  1052.  
  1053. /**
  1054. * Get position of the content node.
  1055. *
  1056. * @method getContentPosition
  1057. * @return {Object} jQuery position object of the content node
  1058. */
  1059. getContentPosition: function () {
  1060. return this._contentNode.position();
  1061. },
  1062.  
  1063. /**
  1064. * Get outer height of the content node
  1065. *
  1066. * @method getContentOuterHeight
  1067. * @return {Number} outer height of the content node
  1068. */
  1069. getContentOuterHeight: function () {
  1070. return this._contentNode.outerHeight();
  1071. }
  1072. });
  1073.  
  1074. lang.mixin(StepItem,
  1075. {
  1076. /**
  1077. * Specifies the current step CSS class name.
  1078. *
  1079. * @property currentStepClass
  1080. * @static
  1081. * @type {String}
  1082. */
  1083. currentStepClass: "current-step",
  1084.  
  1085. /**
  1086. * A collection of possible StepItem states and their names.
  1087. *
  1088. * @propery state
  1089. * @static
  1090. * @type {Object}
  1091. * @example
  1092. * state: {
  1093. * SUCCESS: "step-state-success",
  1094. * ERROR: "step-state-error",
  1095. * DEFAULT: "step-state-default",
  1096. * LOADING: "step-state-loading"
  1097. * }
  1098. *
  1099. */
  1100. state: {
  1101. SUCCESS: "step-state-success",
  1102. ERROR: "step-state-error",
  1103. DEFAULT: "step-state-default",
  1104. LOADING: "step-state-loading"
  1105. },
  1106.  
  1107. /**
  1108. * Event names published by the StepItem
  1109. *
  1110. * @property event
  1111. * @static
  1112. * @type Object
  1113. * @example
  1114. * {
  1115. * CURRENT_STEP_CHANGE: "stepItem/currentStepChange",
  1116. * STATE_CHANGE: "stepItem/stateChange"
  1117. * }
  1118. */
  1119. event: {
  1120. /**
  1121. * Published whenever a StepItem becomes a current step. A current step has a distinct visual style.
  1122. *
  1123. * @event StepItem.event.CURRENT_STEP_CHANGE
  1124. * @param event {Object}
  1125. * @param event.level {Number} Level of the StepItem that became a current step
  1126. * @param event.id {String} Id of the StepItem that became a current step
  1127. */
  1128. CURRENT_STEP_CHANGE: "stepItem/currentStepChange",
  1129.  
  1130. /**
  1131. * Published whenever a StepItem changes its state.
  1132. *
  1133. * @event StepItem.event.CURRENT_STEP_CHANGE
  1134. * @param event {Object}
  1135. * @param event.level {Number} Level of the StepItem that became a current step
  1136. * @param event.id {String} Id of the StepItem that became a current step
  1137. * @param event.state {String} name of the state
  1138. */
  1139. STATE_CHANGE: "stepItem/stateChange"
  1140. }
  1141. }
  1142. );
  1143.  
  1144. // a string with all possible StepItem state CSS classes joined by " "; used to clear any CSS state class from the node
  1145. ALL_STATES_CLASS =
  1146. Object
  1147. .getOwnPropertyNames(StepItem.state)
  1148. .map(function (key) { return StepItem.state[key]; })
  1149. .join(" ");
  1150.  
  1151. return StepItem;
  1152. });