Show:
  1. /**
  2. * HTML5 canvas visual directed graph creation tool
  3. *
  4. * @module raska
  5. * @main installUsing
  6. */
  7. (function (w, d) {
  8. 'use strict';
  9. /**
  10. * An utility that wraps the commom taks to avoid code repetition
  11. *
  12. * @module raska
  13. * @submodule _helpers
  14. */
  15. var $ = d.querySelector.bind(d),
  16. _helpers = (function () {
  17. /// A shim to allow a better animation frame timing
  18. w.requestAnimationFrame = function () {
  19. var _timer = null;
  20. return w.requestAnimationFrame || w.webkitRequestAnimationFrame ||
  21. w.mozRequestAnimationFrame || w.msRequestAnimationFrame ||
  22. w.oRequestAnimationFrame || function (f) {
  23. if (_timer !== null) {
  24. w.clearTimeout(_timer);
  25. }
  26. _timer = w.setTimeout(f, _activeConfiguration.frameRefreshRate);
  27. }
  28. }();
  29. return {
  30. /**
  31. * DOM manipulation related helpers
  32. *
  33. * @class $dom
  34. * @module raska
  35. * @submodule _helpers
  36. * @static
  37. */
  38. $dom: (function () {
  39. var DOMElementHelper = function (ele) {
  40. if (_helpers.$obj.is(ele.raw, "function")) {
  41. ele = ele.raw();
  42. }
  43. return {
  44. /**
  45. * Get/Sets the styling of a given element
  46. *
  47. * @method css
  48. * @param {string} name Style attribute
  49. * @param {string} value Style value
  50. * @chainable
  51. */
  52. css: function (name, value) {
  53. if (_helpers.$obj.isType(name, "string")) {
  54. if (_helpers.$obj.isUndefined(value)) {
  55. return ele.style[name];
  56. }
  57. if (value === "") {
  58. return DOMElementHelper(ele).removeCss(name);
  59. }
  60. ele.style[name] = value;
  61. } else {
  62. for (var attr in name) {
  63. DOMElementHelper(ele).css(attr, name[attr]);
  64. }
  65. }
  66. return DOMElementHelper(ele);
  67. },
  68. /**
  69. * Removes the styling attrribute of a given element
  70. *
  71. * @method css
  72. * @param {string} name Style attribute
  73. * @chainable
  74. */
  75. removeCss: function (name) {
  76. if (ele.style.removeProperty) {
  77. ele.style.removeProperty(name);
  78. } else {
  79. ele.style[name] = "";
  80. }
  81. return DOMElementHelper(ele);
  82. },
  83. /**
  84. * Gets/Sets the value for a given attribute of an HTML element
  85. *
  86. * @method attr
  87. * @param {string} name Attribute name
  88. * @param {string} value Attribute value
  89. * @chainable
  90. */
  91. attr: function (name, value) {
  92. if (_helpers.$obj.isType(name, "string")) {
  93. if (_helpers.$obj.isUndefined(value)) {
  94. return ele.getAttribute(name);
  95. }
  96. ele.setAttribute(name, value);
  97. } else {
  98. for (var attr in name) {
  99. DOMElementHelper(ele).attr(attr, name[attr]);
  100. }
  101. }
  102. return DOMElementHelper(ele);
  103. },
  104. /**
  105. * Retrieves the raw HTML element wraped by this helper
  106. *
  107. * @method raw
  108. * @return {HTMLElement} The element itself
  109. */
  110. raw: function () {
  111. return ele;
  112. },
  113. /**
  114. * Gathers UI iteraction X/Y coordinates from an event
  115. *
  116. * @method getXYPositionFrom
  117. * @param {HTMLElement} container The element that contains the bounding rect we'll use to gather relative positioning data
  118. * @param {event} evt The event we're extracting information from
  119. * @return {x,y} Values
  120. */
  121. getXYPositionFrom: function (evt) {
  122. if (_helpers.$device.isTouch
  123. && _helpers.$obj.is(evt.touches.length, "number")
  124. && evt.touches.length > 0) {
  125. evt = evt.touches[0];
  126. }
  127. var eleRect = ele.getBoundingClientRect();
  128. return {
  129. x: ((evt.clientX - eleRect.left) * (ele.width / eleRect.width)),
  130. y: ((evt.clientY - eleRect.top) * (ele.height / eleRect.height))
  131. };
  132. },
  133. /**
  134. * Creates a new child element relative to this HTML element
  135. *
  136. * @method addChild
  137. * @param {string} type Element node type
  138. * @chainable
  139. */
  140. addChild: function (type) {
  141. if (_helpers.$obj.isType(type, "string")) {
  142. var childElement = $thisDOM.create(type);
  143. ele.appendChild(childElement.raw());
  144. return childElement;
  145. } else {
  146. ele.appendChild(type);
  147. return DOMElementHelper(type);
  148. }
  149. },
  150. /**
  151. * Get the node name for this element
  152. *
  153. * @method type
  154. * @return {string} The node name for this element
  155. */
  156. type: function () {
  157. return ele.nodeName;
  158. },
  159. /**
  160. * Gets the parent node for this element
  161. *
  162. * @method getParent
  163. * @chainable
  164. */
  165. getParent: function () {
  166. return DOMElementHelper((ele.parentElement) ? ele.parentElement : ele.parentNode);
  167. },
  168. /**
  169. * Adds a sibling element
  170. *
  171. * @method addSibling
  172. * @param {string} type Element node type
  173. * @chainable
  174. */
  175. addSibling: function (type) {
  176. return DOMElementHelper(ele).getParent().addChild(type);
  177. },
  178. /**
  179. * Gets\Sets the innerHTML content for the HTML element
  180. *
  181. * @method html
  182. * @param {string} content
  183. * @chainable
  184. */
  185. html: function (content) {
  186. if (_helpers.$obj.isType(content, "string")) {
  187. ele.innerHTML = content;
  188. return DOMElementHelper(ele);
  189. }
  190. return ele.innerHTML;
  191. },
  192. /**
  193. * Gets\Sets the innerText content for the HTML element
  194. *
  195. * @method html
  196. * @param {string} content
  197. * @chainable
  198. */
  199. text: function (content) {
  200. if (_helpers.$obj.isType(content, "string")) {
  201. ele.innerText = content;
  202. return DOMElementHelper(ele);
  203. }
  204. return ele.innerText;
  205. },
  206. /**
  207. * Registers a delegate to a given element event
  208. *
  209. * @method on
  210. * @param {HTMLElement} targetElement The element that we're interested in
  211. * @param {String} iteractionType The event name
  212. * @param {Function} triggerWrapper The delegate
  213. * @chainable
  214. */
  215. on: function (iteractionType, triggerWrapper) {
  216. // modern browsers including IE9+
  217. if (w.addEventListener) { ele.addEventListener(iteractionType, triggerWrapper, false); }
  218. // IE8 and below
  219. else if (w.attachEvent) { ele.attachEvent("on" + iteractionType, triggerWrapper); }
  220. else { ele["on" + iteractionType] = triggerWrapper; }
  221. return DOMElementHelper(ele);
  222. },
  223. /**
  224. * Selects the first occurent of child elements that matches the selector
  225. *
  226. * @method child
  227. * @param {string} selector Element's selector
  228. * @return {DOMElementHelper} The element wraped in a helper object
  229. */
  230. first: function (selector) {
  231. //https://developer.mozilla.org/en-US/docs/Web/CSS/:scope#Browser_compatibility
  232. var result = ele.querySelectorAll(":scope > " + selector);
  233. if (_helpers.$obj.isType(result, "nodelist")) {
  234. return DOMElementHelper(result[0]);
  235. }
  236. return null;
  237. }
  238. };
  239. },
  240. $thisDOM = {
  241. /**
  242. * Creates and returns an element
  243. *
  244. * @method create
  245. * @param {string} type Element node type
  246. * @param {HTMLElement} parent Element's parent node
  247. * @return {DOMElementHelper} The element wraped in a helper object
  248. */
  249. create: function (type, parent) {
  250. var newElement = d.createElement(type);
  251. newElement.id = _helpers.$obj.generateId();
  252. if (_helpers.$obj.isValid(parent)) {
  253. DOMElementHelper(parent).addChild(newElement);
  254. }
  255. return DOMElementHelper(newElement);
  256. },
  257. /**
  258. * Gathers an element using a given selector query
  259. *
  260. * @method get
  261. * @param {string} selector Element's selector
  262. * @return {DOMElementHelper} The element wraped in a helper object
  263. */
  264. get: function (selector) {
  265. var element = _helpers.$obj.isType(selector, "string") ? $(selector) : selector;
  266. return DOMElementHelper(element);
  267. },
  268. /**
  269. * Gathers an element using a given id
  270. *
  271. * @method getById
  272. * @param {string} id Element's id
  273. * @return {DOMElementHelper} The element wraped in a helper object
  274. */
  275. getById: function (id) {
  276. return DOMElementHelper($("#" + id));
  277. }
  278. };
  279. return $thisDOM;
  280. })(),
  281. /**
  282. * Device related helpers
  283. *
  284. * @class $device
  285. * @module raska
  286. * @submodule _helpers
  287. * @static
  288. */
  289. $device: (function () {
  290. var _isTouch = (('ontouchstart' in w)
  291. || (navigator.MaxTouchPoints > 0)
  292. || (navigator.msMaxTouchPoints > 0)),
  293. _this = {
  294. /**
  295. * Whether or not to the current devide is touchscreen
  296. *
  297. * @property isTouch
  298. * @type Bool
  299. */
  300. isTouch: _isTouch,
  301. /**
  302. * Gathers UI iteraction X/Y coordinates from an event
  303. *
  304. * @method gathersXYPositionFrom
  305. * @param {HTMLElement} container The element that contains the bounding rect we'll use to gather relative positioning data
  306. * @param {event} evt The event we're extracting information from
  307. * @return {x,y} Values
  308. */
  309. gathersXYPositionFrom: function (container, evt) {
  310. return _helpers.$dom.get(container).getXYPositionFrom(evt);
  311. },
  312. /**
  313. * Registers a delegate to a given element event
  314. *
  315. * @method on
  316. * @param {HTMLElement} targetElement The element that we're interested in
  317. * @param {String} iteractionType The event name
  318. * @param {Function} triggerWrapper The delegate
  319. * @chainable
  320. */
  321. on: function (targetElement, iteractionType, triggerWrapper) {
  322. _helpers.$dom.get(targetElement).on(iteractionType, triggerWrapper);
  323. return _this;
  324. }
  325. };
  326. return _this;
  327. })(),
  328. /**
  329. * Outputs messages to the 'console'
  330. *
  331. * @class $log
  332. * @module raska
  333. * @submodule _helpers
  334. * @static
  335. */
  336. $log: {
  337. /**
  338. * Whether or not to actually log the messages (should be 'false' in production code)
  339. *
  340. * @property active
  341. * @type Bool
  342. * @default false
  343. */
  344. active: false,
  345. /**
  346. * Prints an informational message to the console
  347. *
  348. * @method info
  349. * @param {String} msg The message to be shown
  350. * @param {Any} o Any extra message data
  351. */
  352. info: function (msg, o) {
  353. if (this.active === true) {
  354. console.info(msg, o);
  355. }
  356. }
  357. },
  358. /**
  359. * Handles commom prototype element' tasks
  360. *
  361. * @class $obj
  362. * @module raska
  363. * @submodule _helpers
  364. * @static
  365. */
  366. $obj: (function () {
  367. var _this;
  368. function s4() {
  369. return Math.floor((1 + Math.random()) * 0x10000)
  370. .toString(16)
  371. .substring(1);
  372. }
  373. function normalizeChain(elementRoot, resultFull) {
  374. var item, links;
  375. for (var i = 0; i < elementRoot.length; i++) {
  376. item = elementRoot[i];
  377. if ((item.graphNode === true) && (((links = item.getLinksFrom()) === null) || (links.length === 0))
  378. && (((links = item.getLinksTo()) === null) || (links.length === 0))) {
  379. throw new _defaultConfigurations.errors.elementDoesNotHaveALink(item.name);
  380. }
  381. if (item.isSerializable()) {
  382. /// Adds the normalized item
  383. resultFull.push(item.normalize());
  384. /// Check for child elements
  385. normalizeChain(item.getChildElements(), resultFull);
  386. }
  387. }
  388. }
  389. return _this = {
  390. /**
  391. * Executes a given delegate for each item in the collection
  392. *
  393. * @method forEach
  394. * @param {Array} arr The array that need to be enumerated
  395. * @param {Delegate} what What to do to a given item (obj item, number index)
  396. * @return Array of data acquired during array enumaration
  397. */
  398. forEach: function (arr, what) {
  399. var result = [];
  400. if (_this.isArray(arr)) {
  401. for (var i = 0; i < arr.length; i++) {
  402. result.push(what(arr[i], i));
  403. }
  404. }
  405. return result;
  406. },
  407. /**
  408. * Whether or not a given object type is of the type you expect (typeof)
  409. *
  410. * @method is
  411. * @param {Object} obj The object we whant to know about
  412. * @param {String} what The string representing the name of the type
  413. * @return {Bool} Whether or not the object matches the specified type
  414. */
  415. is: function (obj, what) {
  416. return typeof obj === what;
  417. },
  418. /**
  419. * Whether or not a given object type is of the type you expect (constructor call)
  420. *
  421. * @method isType
  422. * @param {Object} obj The object we whant to know about
  423. * @param {String} typeName The string representing the name of the type
  424. * @return {Bool} Whether or not the object matches the specified type
  425. */
  426. isType: function (obj, typeName) {
  427. return this.isValid(obj) && Object.prototype.toString.call(obj).toLowerCase() === "[object " + typeName.toLowerCase() + "]";
  428. },
  429. /**
  430. * Generates a pseudo-random Id
  431. *
  432. * @method generateId
  433. * @return {String} A pseudo-random Id
  434. */
  435. generateId: function () {
  436. return "__" + s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4();
  437. },
  438. /**
  439. * Whether or not a given object type is valid to be handled
  440. *
  441. * @method isValid
  442. * @param {Object} obj The object we whant to know if it's valid to be handled
  443. * @return {Bool} Whether or not the object is valid to be handled
  444. */
  445. isValid: function (obj) {
  446. return !this.is(obj, 'undefined') && obj !== null;
  447. },
  448. /**
  449. * Whether or not a given object is an Array
  450. *
  451. * @method isArray
  452. * @param {Object} obj The object we whant to know is it's valid to be handled
  453. * @return {Bool} Whether or not the object is valid to be handled
  454. */
  455. isArray: function (obj) {
  456. return this.isValid(obj) && this.isType(obj, "Array");
  457. },
  458. /**
  459. * Whether or not a given object type is undefined
  460. *
  461. * @method isUndefined
  462. * @param {Object} obj The object we whant to know if it's undefined
  463. * @return {Bool} Whether or not the object is undefined
  464. */
  465. isUndefined: function (obj) {
  466. return this.is(obj, 'undefined');
  467. },
  468. /**
  469. * Serializes a given _basicElement array to a JSON string
  470. *
  471. * @method deconstruct
  472. * @param {_basicElement[]} basicElementArray The array of _basicElement we're going to work with
  473. * @return {string} The corresponding string
  474. */
  475. deconstruct: function (elements) {
  476. var resultFull = [];
  477. if (_helpers.$obj.isArray(elements)) {
  478. normalizeChain(elements, resultFull);
  479. }
  480. return JSON.stringify(resultFull);
  481. },
  482. /**
  483. * Tries to recreate a previously serialized object into a new raska instance
  484. *
  485. * @method recreate
  486. * @param {JSON} jsonElement The JSON representation of a raska object
  487. * @return {_basicElement} The recreated element or null if the element type is invalid
  488. */
  489. recreate: function (jsonElement) {
  490. if (this.isValid(jsonElement.type) && (jsonElement.type in _elementTypes)) {
  491. var resultElement = this.extend(new _defaultConfigurations[jsonElement.type], jsonElement);
  492. resultElement.getType = function () { return jsonElement.type; }
  493. return resultElement;
  494. }
  495. return null;
  496. },
  497. /**
  498. * Recreate all links of the object
  499. *
  500. * @method recreate
  501. */
  502. recreateLinks: function (targetElement, baseElement, findElementDelegate) {
  503. /// Copy data back as it should be
  504. this.forEach(this.forEach(baseElement.linksTo, findElementDelegate),
  505. targetElement.addLinkTo.bind(targetElement));
  506. this.forEach(this.forEach(baseElement.childElements, findElementDelegate),
  507. targetElement.addChild.bind(targetElement));
  508. if (baseElement.parent !== null) {
  509. findElementDelegate(baseElement.parent).addChild(targetElement);
  510. }
  511. },
  512. /**
  513. * Extends any object using a base template object as reference
  514. *
  515. * @method extend
  516. * @param {Object} baseObject The object we whant to copy from
  517. * @param {Object} impl The object with the data we want use
  518. * @param {Bool} addNewMembers Whether or not we allow new members on the 'impl' object to be used in the result
  519. * @return {Object} Extended object
  520. */
  521. extend: function (baseObject, impl, addNewMembers) {
  522. var result = {}, element = null;
  523. if (this.isUndefined(impl)) {
  524. for (element in baseObject) {
  525. result[element] = baseObject[element];
  526. }
  527. } else {
  528. if (addNewMembers === true) { result = impl; }
  529. for (element in baseObject) {
  530. if (!result.hasOwnProperty(element)) {
  531. result[element] = impl.hasOwnProperty(element) ? impl[element] : baseObject[element];
  532. }
  533. }
  534. }
  535. return result;
  536. }
  537. };
  538. })()
  539. };
  540. })(),
  541. /**
  542. * Gathers all elements being ploted to the canvas and organizes it as a directed graph JSON
  543. *
  544. * @private
  545. * @class _graphNodeInfo
  546. * @param {_basicElement} nodeElement Element whose dependencies we're about to transpose searching for links
  547. * @param {_graphNodeInfo} parentNode The parent no to this element
  548. * @param {array} links The linked data to this element
  549. */
  550. _graphNodeInfo = function (nodeElement, parentNode, links) {
  551. var _parent = parentNode || null,
  552. _links = [];
  553. /**
  554. * The element wraped by this node
  555. *
  556. * @property element
  557. * @type _basicElement
  558. * @final
  559. */
  560. this.element = nodeElement;
  561. /**
  562. * Whether or not this node has a parent element
  563. *
  564. * @property hasParent
  565. * @type bool
  566. * @final
  567. */
  568. this.hasParent = function (node) {
  569. return ((_parent !== null)
  570. && ((_parent.element === node) || (_parent.getName() === node.name) || _parent.hasParent(node)));
  571. };
  572. /**
  573. * Tries to find a parent node compatible with the element passed as parameter
  574. *
  575. * @method getParentGraphNodeFor
  576. * @param {_basicElement} bElement Element wraped by a parent node
  577. * @type _graphNodeInfo
  578. */
  579. this.getParentGraphNodeFor = function (bElement) {
  580. if ((this.element === bElement) || (this.getName() === bElement.name)) {
  581. return this;
  582. }
  583. if (_parent !== null) {
  584. return this.getParent(bElement);
  585. }
  586. return null;
  587. };
  588. this.getName = function () {
  589. return nodeElement.name;
  590. };
  591. this.getParent = function () {
  592. return _parent;
  593. };
  594. this.getLinks = function () {
  595. return _links;
  596. };
  597. for (var j = 0; j < links.length; j++) {
  598. if (links[j].graphNode === true) {
  599. if (this.hasParent(links[j])) {
  600. /// We've reached a recursive relation. In this case, simply gathers the reference to the parent and
  601. /// link it here for better navegability between nodes dependecies
  602. _helpers.$log.info("ingored parent node", this.getParentGraphNodeFor(links[j]));
  603. } else {
  604. _links.push(new _graphNodeInfo(links[j], this, links[j].getLinksTo()));
  605. }
  606. }
  607. }
  608. },
  609. /**
  610. * The valid position types for a Raskel element
  611. *
  612. * @private
  613. * @property _positionTypes
  614. * @static
  615. * @final
  616. */
  617. _positionTypes = {
  618. /**
  619. * Element position will be relative to its parent (if any)
  620. *
  621. * @property relative
  622. * @type Number
  623. * @default 50
  624. * @readOnly
  625. */
  626. relative: 0,
  627. /**
  628. * Element position will be absolute and will have no regard about its parent position whatsoever
  629. *
  630. * @property relative
  631. * @type Number
  632. * @default 50
  633. * @readOnly
  634. */
  635. absolute: 1
  636. },
  637. _elementTypes = {
  638. basic: "basic",
  639. html: "htmlElement",
  640. arrow: "arrow",
  641. circle: "circle",
  642. square: "square",
  643. label: "label",
  644. triangle: "triangle"
  645. },
  646. /**
  647. * The basic shape of an Raska element
  648. *
  649. * @private
  650. * @class _basicElement
  651. * @module raska
  652. */
  653. _basicElement = function () {
  654. var
  655. /**
  656. * A simple canvas used to perform a pixel model hit test agains this element
  657. *
  658. * @property $hitTestCanvas
  659. * @type {CanvasRenderingContext2D}
  660. * @protected
  661. */
  662. $hitTestCanvas = _helpers.$dom.create("canvas").attr({ "width": "1", "height": "1" }),
  663. $hitTestContext = $hitTestCanvas.raw().getContext("2d"),
  664. /**
  665. * A simple helper function to be used whenever this instance gets disable (if ever)
  666. *
  667. * @private
  668. * @method $disabled
  669. */
  670. $disabled = function () { },
  671. $wasDisabled = false,
  672. /**
  673. * Points to a parent Raska element (if any)
  674. *
  675. * @private
  676. * @property $parent
  677. * @default null
  678. */
  679. $parent = null,
  680. /**
  681. * Points to a Raska element [as an arrow] (if any) related as a dependency from this node
  682. *
  683. * @private
  684. * @property $linksTo
  685. * @type _basicElement
  686. * @default []
  687. */
  688. $linksTo = [],
  689. /**
  690. * Points to a Raska element [as an arrow] (if any) that depends on this instance
  691. *
  692. * @private
  693. * @property $linksFrom
  694. * @type _basicElement
  695. * @default []
  696. */
  697. $linksFrom = [],
  698. /**
  699. * Holds the reference to the delegate triggered before a link gets removed from an element
  700. * @property $beforeRemoveLinkFrom
  701. * @type function
  702. * @default null
  703. */
  704. $beforeRemoveLinkFrom = null,
  705. /**
  706. * Holds the reference to the delegate triggered before a link gets removed to an element
  707. * @property $beforeRemoveLinkTo
  708. * @type function
  709. * @default null
  710. */
  711. $beforeRemoveLinkTo = null,
  712. /**
  713. * Points to series of child Raska element (if any) that, usually, are contained inside this node
  714. *
  715. * @private
  716. * @property $childElements
  717. * @type _basicElement
  718. * @default []
  719. */
  720. $childElements = [], $this, $disabledStateSubscribers = [];
  721. /**
  722. * Clears all links that point to and from this Raska element
  723. *
  724. * @private
  725. * @method clearLinksFrom
  726. * @param {Object} source Object that defines the source of the link removal (start node)
  727. * @param {Function} each Delegates that handle the process of removing the references
  728. * @return {Array} Empty array
  729. */
  730. function clearLinksFrom(source, each) {
  731. for (var i = 0; i < source.length; i++) {
  732. each(source[i]);
  733. }
  734. return [];
  735. }
  736. return $this = {
  737. /**
  738. * Just an empty attribute bag where you can save extra stuff that you need/want to serialize
  739. *
  740. * @property attr
  741. * @default "{}"
  742. * @type Object
  743. */
  744. attr: {},
  745. /**
  746. * The element name
  747. *
  748. * @property name
  749. * @default "anonymous"
  750. * @type String
  751. */
  752. name: "anonymous",
  753. /**
  754. * The element type
  755. *
  756. * @property getType
  757. * @default _elementTypes.basic
  758. * @type _elementTypes
  759. */
  760. getType: function () { return _elementTypes.basic; },
  761. /**
  762. * Wheter or not this element is part of the graph as a node
  763. *
  764. * @property graphNode
  765. * @default true
  766. * @type Bool
  767. */
  768. graphNode: true,
  769. /**
  770. * Allows to better check whether or not a new link can be created beteen two elements
  771. *
  772. * @method canLink
  773. * @param {_basicElement} from Element that will be linked as a source
  774. * @param {_basicElement} to Element that will be linked as a destination
  775. * @return {Bool} Whether or not the link can be created
  776. */
  777. canLink: function (from, to) {
  778. if (from === this) {
  779. return _helpers.$obj.isValid(to) && (to.canReach(this) === false);
  780. }
  781. return true;
  782. },
  783. /**
  784. * Clears all references and child elements from this instance
  785. *
  786. * @method disable
  787. * @chainable
  788. */
  789. disable: function () {
  790. $wasDisabled = true;
  791. _helpers.$obj.forEach($disabledStateSubscribers, function (target) {
  792. if (_helpers.$obj.is(target, "function")) {
  793. target(this);
  794. } else {
  795. target.elementDisabledNotification(this);
  796. }
  797. }.bind(this));
  798. $disabledStateSubscribers.length = 0; /// Let us free some space
  799. this.clearAllLinks();
  800. _helpers.$obj.forEach($childElements, function (ele) { ele.disable(); });
  801. $childElements.length = 0;
  802. this.drawTo = $disabled;
  803. return this;
  804. },
  805. /**
  806. * Notifies a given element whenever (if ever) this element gets disabled
  807. *
  808. * @method notifyDisableStateOn
  809. * @chainable
  810. */
  811. notifyDisableStateOn: function (target) {
  812. if (($disabledStateSubscribers.indexOf(target) === -1)
  813. && (_helpers.$obj.isValid(target.elementDisabledNotification) || _helpers.$obj.is(target, "function"))) {
  814. $disabledStateSubscribers.push(target);
  815. }
  816. return this;
  817. },
  818. /**
  819. * Handles element disabling notifications
  820. *
  821. * @method elementDisabledNotification
  822. * @chainable
  823. */
  824. elementDisabledNotification: function (element) {
  825. return this;
  826. },
  827. /**
  828. * Whether or not this element can reach another element via a linked relation
  829. *
  830. * @method canReach
  831. * @return {Bool} Whether or not a link exists
  832. */
  833. canReach: function (node) {
  834. if ($linksTo.length > 0) {
  835. for (var i = 0; i < $linksTo.length; i++) {
  836. if ($linksTo[i].canReach(node)) {
  837. return true;
  838. }
  839. }
  840. }
  841. return (this === node);
  842. },
  843. /**
  844. * Is this element passive of being linked at all?
  845. *
  846. * @method isLinkable
  847. * @return {Bool} Whether or not a link can be created either from or to this element
  848. */
  849. isLinkable: function () {
  850. return !$wasDisabled;
  851. },
  852. /**
  853. * Is this element passive of being serialized at all?
  854. *
  855. * @method isSerializable
  856. * @return {Bool} Whether or not this element is suposed to be serialized
  857. */
  858. isSerializable: function () {
  859. return !$wasDisabled;
  860. },
  861. /**
  862. * Creates a link between this and another element, using the later as a dependency of this instance
  863. *
  864. * @method addLinkTo
  865. * @param {_basicElement} element Element that will be linked as a destination from this element node
  866. * @return {Bool} Whether or not the link was added
  867. */
  868. addLinkTo: function (element) {
  869. if (_helpers.$obj.isValid(element.isLinkable) && element.isLinkable()
  870. && this.isLinkable() && ($linksTo.indexOf(element) === -1)
  871. && (element !== this) && this.canLink(this, element)) {
  872. $linksTo.push(element);
  873. element.addLinkFrom(this);
  874. return true;
  875. }
  876. return false;
  877. },
  878. /**
  879. * Links this element to another Raska element as this instance being the target node
  880. *
  881. * @method addLinkFrom
  882. * @param {_basicElement} element Element that will be linked as a source from this element node
  883. * @return {Bool} Whether or not the link was added
  884. */
  885. addLinkFrom: function (element) {
  886. if (_helpers.$obj.isValid(element.isLinkable) && element.isLinkable()
  887. && this.isLinkable() && ($linksFrom.indexOf(element) === -1)
  888. && (element !== this) && this.canLink(element, this)) {
  889. $linksFrom.push(element);
  890. element.addLinkTo(this);
  891. return true;
  892. }
  893. return false;
  894. },
  895. /**
  896. * Removes the dependency from this element to another Raska element
  897. *
  898. * @method removeLinkTo
  899. * @param {_basicElement} element Element that will have its link to this element removed
  900. * @return {_basicElement} Updated Raska element reference
  901. * @chainable
  902. */
  903. removeLinkTo: function (element) {
  904. if ($linksTo.indexOf(element) > -1) {
  905. $linksTo.splice($linksTo.indexOf(element), 1);
  906. if (_helpers.$obj.isType($beforeRemoveLinkFrom, "function") === true) {
  907. $beforeRemoveLinkFrom.call(this, element);
  908. }
  909. element.removeLinkFrom(this);
  910. }
  911. return this;
  912. },
  913. /**
  914. * Event triggered before a link gets removed from this element
  915. *
  916. * @method beforeRemoveLinkFrom
  917. * @param {function} doWhat A delegate with the 'this' poiting to this instance and the first
  918. * paramenter being the reference to the element which is being remove as a link to this instance
  919. * @chainable
  920. */
  921. beforeRemoveLinkFrom: function (doWhat) {
  922. $beforeRemoveLinkFrom = doWhat;
  923. return this;
  924. },
  925. /**
  926. * Transposes the getter data to a field attached to this instance
  927. *
  928. * @method normalize
  929. * @chainable
  930. */
  931. normalize: function () {
  932. var n = function (item) { return item.name; };
  933. var normalized = _helpers.$obj.extend(this, {
  934. linksTo: _helpers.$obj.forEach(this.getLinksTo(), n),
  935. childElements: _helpers.$obj.forEach(this.getChildElements(), n),
  936. parent: _helpers.$obj.isValid($parent) ? $parent.name : null,
  937. type: this.getType()
  938. }, true);
  939. delete normalized.on; /// We don't want/need this
  940. return normalized;
  941. },
  942. /**
  943. * Gathers all elements that this instance depends on
  944. *
  945. * @method getLinksTo
  946. * @return {Array} All elements that this node references TO
  947. */
  948. getLinksTo: function () {
  949. return $linksTo;
  950. },
  951. /**
  952. * Gathers all dependecies/linked elements related to this instance
  953. *
  954. * @method getLinksFrom
  955. * @return {Array} All elements that this node references from
  956. */
  957. getLinksFrom: function () {
  958. return $linksFrom;
  959. },
  960. /**
  961. * Removes the link to this element from another Raska element
  962. *
  963. * @method removeLinkFrom
  964. * @param {_basicElement} element Element that will have its link from this element removed
  965. * @return {_basicElement} Updated Raska element reference
  966. * @chainable
  967. */
  968. removeLinkFrom: function (element) {
  969. if ($linksFrom.indexOf(element) > -1) {
  970. $linksFrom.splice($linksFrom.indexOf(element), 1);
  971. if (_helpers.$obj.isType($beforeRemoveLinkTo, "function") === true) {
  972. $beforeRemoveLinkTo.call(this, element);
  973. }
  974. element.removeLinkTo(this);
  975. }
  976. return this;
  977. },
  978. /**
  979. * Event triggered before a link gets removed to this element
  980. *
  981. * @method beforeRemoveLinkTo
  982. * @param {function} doWhat A delegate with the 'this' poiting to this instance and the first
  983. * paramenter being the reference to the element which is being remove as a link from this instance
  984. * @chainable
  985. */
  986. beforeRemoveLinkTo: function (doWhat) {
  987. $beforeRemoveLinkTo = doWhat;
  988. return this;
  989. },
  990. /**
  991. * Removes every and each link that references this element
  992. *
  993. * @method clearAllLinks
  994. * @return {_basicElement} Updated Raska element reference
  995. * @chainable
  996. */
  997. clearAllLinks: function () {
  998. $linksTo = clearLinksFrom($linksTo, function (e) { e.removeLinkFrom(this); }.bind(this));
  999. $linksFrom = clearLinksFrom($linksFrom, function (e) { e.removeLinkTo(this); }.bind(this));
  1000. return this;
  1001. },
  1002. /**
  1003. * Gathers all child elements from this node
  1004. *
  1005. * @method getChildElements
  1006. * @return {Array} All child elements
  1007. */
  1008. getChildElements: function () {
  1009. return $childElements;
  1010. },
  1011. /**
  1012. * Adds a new child element to this node
  1013. *
  1014. * @method addChild
  1015. * @param {_basicElement} child Element that will be added to the child array into this element
  1016. * @return {_basicElement} Updated Raska element reference
  1017. * @chainable
  1018. */
  1019. addChild: function (child) {
  1020. child.setParent(this);
  1021. $childElements.push(child);
  1022. return this;
  1023. },
  1024. /**
  1025. * Get the parent node to this element
  1026. *
  1027. * @method getParent
  1028. * @return {_basicElement} Reference to de parent node (null if none)
  1029. */
  1030. getParent: function () {
  1031. return $parent;
  1032. },
  1033. /**
  1034. * Retrieves the element boundaries information
  1035. *
  1036. * @method getBoundaries
  1037. * @return {Object} Data regarding x, y, minX and minY from this element
  1038. */
  1039. getBoundaries: function () {
  1040. return {
  1041. x: this.getWidth(),
  1042. y: this.getHeight(),
  1043. minX: 0,
  1044. minY: 0
  1045. };
  1046. },
  1047. /**
  1048. * Sets the parent node to this element
  1049. *
  1050. * @method setParent
  1051. * @param {_basicElement} parent Element that will be added as a parent to this element
  1052. * @return {_basicElement} Updated Raska element reference
  1053. * @chainable
  1054. */
  1055. setParent: function (parent) {
  1056. $parent = parent;
  1057. return this;
  1058. },
  1059. /**
  1060. * Details regarding this element' border
  1061. *
  1062. * @attribute border
  1063. */
  1064. border: {
  1065. /**
  1066. * Element border color
  1067. *
  1068. * @property color
  1069. * @type String
  1070. * @default null
  1071. */
  1072. color: null,
  1073. /**
  1074. * Whether or not a border should be rendered
  1075. *
  1076. * @property active
  1077. * @type Bool
  1078. * @default false
  1079. */
  1080. active: false,
  1081. /**
  1082. * Border width
  1083. *
  1084. * @property width
  1085. * @type Number
  1086. * @default 0
  1087. */
  1088. width: 0
  1089. },
  1090. /**
  1091. * Element position type
  1092. *
  1093. * @property position
  1094. * @type _positionTypes
  1095. * @default _positionTypes.relative
  1096. */
  1097. position: _positionTypes.relative,
  1098. /**
  1099. * Element x position
  1100. *
  1101. * @property x
  1102. * @type Number
  1103. * @default 50
  1104. */
  1105. x: 50,
  1106. /**
  1107. * Element y position
  1108. *
  1109. * @property y
  1110. * @type Number
  1111. * @default 50
  1112. */
  1113. y: 50,
  1114. /**
  1115. * Gathers adjusted x/y coordinates for the currente element taking into consideration
  1116. * the type of positining set to it and wheter or not a parent node is present
  1117. *
  1118. * @method getAdjustedCoordinates
  1119. * @return {x:Number,y:Number} Adjusted coordinates
  1120. */
  1121. getAdjustedCoordinates: function () {
  1122. if ((this.position === _positionTypes.relative) && ($parent !== null)) {
  1123. var adjustedParent = $parent.getAdjustedCoordinates();
  1124. return {
  1125. x: this.x + adjustedParent.x,
  1126. y: this.y + adjustedParent.y
  1127. };
  1128. }
  1129. return { x: this.x, y: this.y };
  1130. },
  1131. /**
  1132. * Whether or not this element handle event interactions
  1133. *
  1134. * @property canHandleEvents
  1135. * @type bool
  1136. * @default false
  1137. */
  1138. canHandleEvents: function () { return true; },
  1139. on: (function () {
  1140. var __clickDelegates = [],
  1141. __releaseDelegates = [];
  1142. function triggerDelegatesUsing(x, y, ele, evt, arr) {
  1143. _helpers.$obj.forEach(arr, function (el) {
  1144. el(x, y, ele, evt);
  1145. });
  1146. }
  1147. return {
  1148. /**
  1149. * Triggered whenever a click iteraction occurs within the boundaries of this element.
  1150. * - This event is only supported on selected elements. Check for the *canHandleEvents*
  1151. * property value before relying on this delegate
  1152. *
  1153. * @function click
  1154. * @param {number} x Element's current X position
  1155. * @param {number} y Element's current Y position
  1156. * @param {_basicElement} ele The element that was clicked
  1157. * @param {event} evt Event that triggered the delegate
  1158. * @chainable
  1159. */
  1160. click: function (x, y, ele, evt) {
  1161. if (_helpers.$obj.isType(x, "number") === true) {
  1162. var foundInner = false;
  1163. if ($childElements.length > 0) {
  1164. var parentAdjustedPosition = ele.getAdjustedCoordinates();
  1165. _helpers.$obj.forEach($childElements, function (el) {
  1166. var newPosition = {
  1167. x: x - parentAdjustedPosition.x,
  1168. y: y - parentAdjustedPosition.y
  1169. }
  1170. if (el.canHandleEvents() && el.existsIn(newPosition.x, newPosition.y)) {
  1171. el.on.click(newPosition.x, newPosition.y, el, evt);
  1172. foundInner = true;
  1173. }
  1174. });
  1175. }
  1176. if (foundInner === false) {
  1177. triggerDelegatesUsing(x, y, ele, evt, __clickDelegates);
  1178. }
  1179. } else if (_helpers.$obj.isType(x, "function") === true) {
  1180. __clickDelegates.push(x);
  1181. }
  1182. return $this;
  1183. },
  1184. /**
  1185. * Triggered whenever a 'clickUp' iteraction occurs within the boundaries of this element.
  1186. * - This event is only supported on selected elements. Check for the *canHandleEvents*
  1187. * property value before relying on this delegate
  1188. *
  1189. * @function release
  1190. * @param {number} x Element's current X position
  1191. * @param {number} y Element's current Y position
  1192. * @param {_basicElement} ele The element that was clicked
  1193. * @param {event} evt Event that triggered the delegate
  1194. * @chainable
  1195. */
  1196. release: function (x, y, ele, evt) {
  1197. if (_helpers.$obj.isType(x, "number") === true) {
  1198. var foundInner = false;
  1199. if ($childElements.length > 0) {
  1200. var parentAdjustedPosition = ele.getAdjustedCoordinates();
  1201. _helpers.$obj.forEach($childElements, function (el) {
  1202. var newPosition = {
  1203. x: x - parentAdjustedPosition.x,
  1204. y: y - parentAdjustedPosition.y
  1205. }
  1206. if (el.canHandleEvents() && el.existsIn(newPosition.x, newPosition.y)) {
  1207. el.on.release(newPosition.x, newPosition.y, el, evt);
  1208. foundInner = true;
  1209. }
  1210. });
  1211. }
  1212. if (foundInner === false) {
  1213. triggerDelegatesUsing(x, y, ele, evt, __releaseDelegates);
  1214. }
  1215. } else if (_helpers.$obj.isType(x, "function") === true) {
  1216. __releaseDelegates.push(x);
  1217. }
  1218. return $this;
  1219. }
  1220. };
  1221. })(),
  1222. /**
  1223. * Whether or not this element existis withing the boudaries of
  1224. * the given x/y coordinates
  1225. *
  1226. * @method existsIn
  1227. * @param {Number} x X Coordinate
  1228. * @param {Number} y Y Coordinate
  1229. * @return {Bool} If this element is contained within the X/Y coordinates
  1230. */
  1231. existsIn: function (x, y) {
  1232. $hitTestContext.setTransform(1, 0, 0, 1, -x, -y);
  1233. this.drawTo($hitTestCanvas, $hitTestContext);
  1234. var hit = false;
  1235. try { hit = $hitTestContext.getImageData(0, 0, 1, 1).data[3] > 1; }
  1236. catch (e) { }
  1237. $hitTestContext.setTransform(1, 0, 0, 1, 0, 0);
  1238. $hitTestContext.clearRect(0, 0, 2, 2);
  1239. return hit;
  1240. },
  1241. /**
  1242. * [ABSTRACT] Adjusts the position of the current element taking in consideration it's parent
  1243. * positioning constraints
  1244. *
  1245. * @method adjustPosition
  1246. * @param {Number} newX X position
  1247. * @param {Number} newY Y position
  1248. * @chainable
  1249. */
  1250. adjustPosition: function (newX, newY) {
  1251. console.error(_defaultConfigurations.errors.notImplementedException);
  1252. throw _defaultConfigurations.errors.notImplementedException;
  1253. },
  1254. /**
  1255. * [ABSTRACT] Sets the current width for this element
  1256. *
  1257. * @method setWidth
  1258. * @param {Number} newWidth The width for this element
  1259. * @chainable
  1260. * @throws {_defaultConfigurations.errors.notImplementedException} Not implemented
  1261. */
  1262. setWidth: function (newWidth) {
  1263. console.error(_defaultConfigurations.errors.notImplementedException);
  1264. throw _defaultConfigurations.errors.notImplementedException;
  1265. },
  1266. /**
  1267. * [ABSTRACT] Gets the current width for this element
  1268. *
  1269. * @method getWidth
  1270. * @return {Number} The width of this element
  1271. * @throws {_defaultConfigurations.errors.notImplementedException} Not implemented
  1272. */
  1273. getWidth: function () {
  1274. console.error(_defaultConfigurations.errors.notImplementedException);
  1275. throw _defaultConfigurations.errors.notImplementedException;
  1276. },
  1277. getAdjustedWidth: function () { return this.getWidth(); },
  1278. /**
  1279. * [ABSTRACT] Sets the current Height for this element
  1280. *
  1281. * @method setHeight
  1282. * @param {Number} newHeight The height for this element
  1283. * @chainable
  1284. * @throws {_defaultConfigurations.errors.notImplementedException} Not implemented
  1285. */
  1286. setHeight: function (newHeight) {
  1287. console.error(_defaultConfigurations.errors.notImplementedException);
  1288. throw _defaultConfigurations.errors.notImplementedException;
  1289. },
  1290. /**
  1291. * [ABSTRACT] Gets the current Height for this element
  1292. *
  1293. * @method getHeight
  1294. * @return {Number} The Height of this element
  1295. * @throws {_defaultConfigurations.errors.notImplementedException} Not implemented
  1296. */
  1297. getHeight: function () {
  1298. console.error(_defaultConfigurations.errors.notImplementedException);
  1299. throw _defaultConfigurations.errors.notImplementedException;
  1300. },
  1301. getAdjustedHeight: function () { return this.getHeight(); },
  1302. /**
  1303. * [ABSTRACT] Whether or not this element existis withing the boudaries of
  1304. * the given x/y coordinates
  1305. *
  1306. * @method existsIn
  1307. * @param {HTMLElement|Node} The Canvas element
  1308. * @param {Node} tdContext Canvas' 2d context
  1309. * @throws {_defaultConfigurations.errors.notImplementedException} Not implemented
  1310. */
  1311. drawTo: function (canvasElement, tdContext) {
  1312. console.error(_defaultConfigurations.errors.notImplementedException);
  1313. throw _defaultConfigurations.errors.notImplementedException;
  1314. }
  1315. };
  1316. },
  1317. /**
  1318. * A utility module to control complex canvas' (HTML) iteraction
  1319. *
  1320. * @private
  1321. * @module raska
  1322. * @submodule _canvasController
  1323. * @readOnly
  1324. */
  1325. _canvasController = (function () {
  1326. var _inFullscreen = false,
  1327. _defaultValues = {
  1328. container: {
  1329. "width": 0,
  1330. "height": 0,
  1331. "position": 0,
  1332. "z-index": 0,
  1333. "left": 0,
  1334. "top": 0
  1335. },
  1336. canvas: {
  1337. "width": 0,
  1338. "height": 0
  1339. }
  1340. };
  1341. return {
  1342. /**
  1343. * Toggles canvas in/out fullscreen mode
  1344. *
  1345. * @method toggleFullScreen
  1346. * @param {HTMLElement|Node} The Canvas element
  1347. * @param {string} strContainerId The id for the canvas toolbar (or undefined if nothing needs to be done there)
  1348. */
  1349. toggleFullScreen: function (canvas, strContainerId) {
  1350. var canvasElementContainer = _helpers.$dom.getById(_activeConfiguration.targetCanvasContainerId),
  1351. canvasElement = _helpers.$dom.get(canvas),
  1352. bottomPadding = strContainerId ? 40/*the estimated Height size for the toolbox*/ : 0;
  1353. if (_inFullscreen === false) {
  1354. /// Recovery mode for the container
  1355. for (var attr in _defaultValues.container) {
  1356. _defaultValues.container[attr] = canvasElementContainer.css(attr);
  1357. }
  1358. canvasElementContainer.css({
  1359. "width": d.body.clientWidth,
  1360. "height": d.body.clientHeight - bottomPadding,
  1361. "position": "fixed",
  1362. "z-index": 1000,
  1363. "background-color": "white",
  1364. "left": 0,
  1365. "top": 0
  1366. });
  1367. /// Recovery mode for the canvas
  1368. for (var attr in _defaultValues.canvas) {
  1369. _defaultValues.canvas[attr] = canvasElement.attr(attr);
  1370. }
  1371. canvasElement.attr({ "width": d.body.clientWidth, "height": d.body.clientHeight - bottomPadding });
  1372. if (_helpers.$obj.isValid(strContainerId)) {
  1373. _helpers.$dom.getById(strContainerId)
  1374. .first("button")
  1375. .html("<span class='glyphicon glyphicon-resize-small'></span>&nbsp;Back to normal");
  1376. }
  1377. _inFullscreen = true;
  1378. } else {
  1379. for (var attr in _defaultValues.container) {
  1380. canvasElementContainer.css(attr, _defaultValues.container[attr]);
  1381. }
  1382. for (var attr in _defaultValues.canvas) {
  1383. canvasElement.attr(attr, _defaultValues.canvas[attr]);
  1384. }
  1385. if (_helpers.$obj.isValid(strContainerId)) {
  1386. _helpers.$dom.getById(strContainerId)
  1387. .first("button")
  1388. .html("<span class='glyphicon glyphicon-resize-full'></span>&nbsp;Fullscreen");
  1389. }
  1390. _inFullscreen = false;
  1391. }
  1392. }
  1393. };
  1394. })(),
  1395. /**
  1396. * A utility container that holds default instance information for the Raska library
  1397. *
  1398. * @private
  1399. * @module raska
  1400. * @submodule _defaultConfigurations
  1401. * @readOnly
  1402. */
  1403. _defaultConfigurations = {
  1404. /**
  1405. * Raska's default configuration data
  1406. *
  1407. * @property library
  1408. * @static
  1409. * @final
  1410. */
  1411. library: {
  1412. readonly: false,
  1413. frameRefreshRate: 30,
  1414. targetCanvasId: "",
  1415. targetCanvasContainerId: "____raska" + _helpers.$obj.generateId(),
  1416. toolboxButtons: [
  1417. {
  1418. /// THIS WILL BE SET AUTOMATICALLY WHEN THE BUTTON GETS RENDERED
  1419. id: "", /// THIS WILL BE SET AUTOMATICALLY WHEN THE BUTTON GETS RENDERED
  1420. /// THIS WILL BE SET AUTOMATICALLY WHEN THE BUTTON GETS RENDERED
  1421. name: "fullscreen",
  1422. enabled: true,
  1423. onclick: function (canvas) {
  1424. _canvasController.toggleFullScreen(canvas, this.id);
  1425. },
  1426. template: "<button class='btn btn-primary btn-sm'><span class='glyphicon glyphicon-resize-full'></span>&nbsp;Fullscreen</button>"
  1427. }
  1428. ]
  1429. },
  1430. /**
  1431. * Raska's exception types
  1432. *
  1433. * @property errors
  1434. * @static
  1435. * @final
  1436. */
  1437. errors: {
  1438. notImplementedException: {
  1439. message: "Not implemented",
  1440. code: 0
  1441. },
  1442. nullParameterException: function (parameterName) {
  1443. if ((typeof parameterName === 'undefined') || (parameterName === null)) {
  1444. throw new this.nullParameterException("parameterName");
  1445. }
  1446. this.message = "Parameter can't be null";
  1447. this.parameterName = parameterName;
  1448. this.code = 1;
  1449. },
  1450. elementDoesNotHaveALink: function (elementName) {
  1451. if ((typeof elementName === 'undefined') || (elementName === null)) {
  1452. throw new this.nullParameterException("elementName");
  1453. }
  1454. return {
  1455. message: "Element '" + elementName + "' doesn't have a valid linked sibling",
  1456. elementName: elementName,
  1457. code: 2
  1458. };
  1459. },
  1460. itemNotFoundException: function (elementName) {
  1461. if ((typeof elementName === 'undefined') || (elementName === null)) {
  1462. throw new this.nullParameterException("elementName");
  1463. }
  1464. return {
  1465. message: "Element '" + elementName + "' wasn't found",
  1466. elementName: elementName,
  1467. code: 3
  1468. };
  1469. }
  1470. },
  1471. /**
  1472. * Creates a label
  1473. *
  1474. * @class label
  1475. * @extends _basicElement
  1476. */
  1477. label: function () {
  1478. return _helpers.$obj.extend(new _basicElement(), {
  1479. name: "label" + _helpers.$obj.generateId(),
  1480. graphNode: false,
  1481. getType: function () { return _elementTypes.label; },
  1482. canLink: function () { return false; },
  1483. isLinkable: function () { return false; },
  1484. text: "",
  1485. color: "gray",
  1486. x: 0,
  1487. y: 0,
  1488. border: { color: "white", active: true, width: 2 },
  1489. font: { family: "Arial", size: "12px", decoration: "" },
  1490. getWidth: function () {
  1491. return this.text.length * 5;
  1492. },
  1493. getHeight: function () {
  1494. return this.font.size;
  1495. },
  1496. drawTo: function (canvas, context) {
  1497. var coordinates = this.getAdjustedCoordinates();
  1498. context.save();
  1499. context.font = (this.font.decoration || "") + " " + this.font.size + " " + this.font.family;
  1500. if (this.border.active === true) {
  1501. context.lineJoin = "round";
  1502. context.lineWidth = this.border.width;
  1503. context.strokeStyle = this.border.color;
  1504. context.strokeText(this.text, coordinates.x, coordinates.y);
  1505. }
  1506. context.fillStyle = this.color;
  1507. context.fillText(this.text, coordinates.x, coordinates.y);
  1508. context.restore();
  1509. },
  1510. existsIn: function (x, y) {
  1511. /// Efective as a non-draggable element
  1512. return false;
  1513. }
  1514. }, true);
  1515. },
  1516. /**
  1517. * Creates a square
  1518. *
  1519. * @class square
  1520. * @extends _basicElement
  1521. */
  1522. square: function (desiredDimensions) {
  1523. var _dimensions = desiredDimensions || {
  1524. width: 50,
  1525. height: 50
  1526. }, ctx = null;
  1527. return _helpers.$obj.extend(new _basicElement(), {
  1528. name: "square" + _helpers.$obj.generateId(),
  1529. getType: function () { return _elementTypes.square; },
  1530. border: { color: "gray", active: true, width: 2 },
  1531. fillColor: "silver",
  1532. dimensions: _dimensions,
  1533. globalAlpha: 1,
  1534. getWidth: function () {
  1535. return this.dimensions.width;
  1536. },
  1537. getHeight: function () {
  1538. return this.dimensions.height;
  1539. },
  1540. drawTo: function (canvas, context) {
  1541. var coordinates = this.getAdjustedCoordinates();
  1542. context.beginPath();
  1543. context.rect(coordinates.x, coordinates.y, this.dimensions.width, this.dimensions.height);
  1544. context.fillStyle = this.fillColor;
  1545. if (this.border.active === true) {
  1546. context.lineWidth = this.border.width;
  1547. context.strokeStyle = this.border.color;
  1548. }
  1549. context.globalAlpha = this.globalAlpha;
  1550. context.fill();
  1551. context.stroke();
  1552. },
  1553. existsIn: function (x, y) {
  1554. return ((x >= this.x) && (x <= this.x + this.dimensions.width)
  1555. && (y >= this.y) && (y <= this.y + this.dimensions.height));
  1556. },
  1557. adjustPosition: function (newX, newY) {
  1558. var $parent = this.getParent();
  1559. if ((this.position === _positionTypes.relative) && ($parent !== null)) {
  1560. var adjustedParent = $parent.getAdjustedCoordinates();
  1561. var w = $parent.getWidth() / 2;
  1562. var h = $parent.getHeight() / 2;
  1563. var diffX = (newX - adjustedParent.x) - w;
  1564. var diffH = (newY - adjustedParent.y) - h;
  1565. this.x = diffX < 0 ? Math.max(diffX, w * -1) : Math.min(diffX, w);;
  1566. this.y = diffH < 0 ? Math.max(diffH, h * -1) : Math.min(diffH, h);
  1567. } else {
  1568. this.x = newX;
  1569. this.y = newY;
  1570. }
  1571. return this;
  1572. }
  1573. }, true);
  1574. },
  1575. /**
  1576. * Creates an arrow
  1577. *
  1578. * @class arrow
  1579. * @extends _basicElement
  1580. */
  1581. arrow: function (target) {
  1582. if (_helpers.$obj.isUndefined(target) || (target === null)) {
  1583. throw new _defaultConfigurations.errors.nullParameterException("target");
  1584. }
  1585. var _target = target,
  1586. _drawArrowhead = function (context, x, y, radians, sizeW, sizeH, arrowColor) {
  1587. context.fillStyle = arrowColor;
  1588. context.save();
  1589. context.beginPath();
  1590. context.translate(x, y);
  1591. context.rotate(radians);
  1592. context.moveTo(0, -10);
  1593. context.lineTo(sizeH, sizeW);
  1594. context.lineTo(sizeH * -1, sizeW);
  1595. context.closePath();
  1596. context.restore();
  1597. context.fill();
  1598. };
  1599. return _helpers.$obj.extend(new _basicElement(), {
  1600. name: "arrow" + _helpers.$obj.generateId(),
  1601. clearAllLinks: function () {
  1602. if (_helpers.$obj.isValid(this.getParent())
  1603. && _helpers.$obj.is(this.getParent().removeLinkFrom, "function")
  1604. && _helpers.$obj.is(_target.removeLinkTo, "function")) {
  1605. this.getParent().removeLinkFrom(_target).removeLinkTo(_target);
  1606. _target.removeLinkFrom(this.getParent()).removeLinkTo(this.getParent());
  1607. }
  1608. return this;
  1609. },
  1610. elementDisabledNotification: function (element) {
  1611. if ((element === _target) || (element === this.getParent())) {
  1612. this.disable();
  1613. }
  1614. },
  1615. graphNode: false,
  1616. canHandleEvents: function () { return false; },
  1617. isSerializable: function () { return false; },
  1618. getType: function () { return _elementTypes.arrow; },
  1619. canLink: function () { return false; },
  1620. isLinkable: function () { return false; },
  1621. border: { color: "gray", active: true, width: 2 },
  1622. fillColor: "rgba(0,0,0,0.3)",
  1623. getWidth: function () { return 1; },
  1624. getHeight: function () { return 1; },
  1625. drawTo: function (canvas, context) {
  1626. if (_helpers.$obj.isValid(_target.notifyDisableStateOn)) {
  1627. _target.notifyDisableStateOn(this);
  1628. }
  1629. if (_helpers.$obj.isValid(this.getParent().notifyDisableStateOn)) {
  1630. this.getParent().notifyDisableStateOn(this);
  1631. }
  1632. var adjustedTargedCoordinates = _target.getAdjustedCoordinates ? _target.getAdjustedCoordinates() : { x: _target.x, y: _target.y },
  1633. parent = this.getParent(),
  1634. adjustedParentCoordinates = parent.getAdjustedCoordinates(),
  1635. parentX = (adjustedParentCoordinates.x + (parent.getAdjustedWidth ? (parent.getAdjustedWidth() / 2) : 0)),
  1636. parentY = (adjustedParentCoordinates.y + (parent.getAdjustedHeight ? (parent.getAdjustedHeight() / 2) : 0));
  1637. this.x = (adjustedTargedCoordinates.x + (_target.getAdjustedWidth ? (_target.getAdjustedWidth() / 2) : 0));
  1638. this.y = (adjustedTargedCoordinates.y + (_target.getAdjustedHeight ? (_target.getAdjustedHeight() / 2) : 0));
  1639. context.beginPath();
  1640. context.fillStyle = this.fillColor;
  1641. if (this.border.active === true) {
  1642. var grad = context.createLinearGradient(this.x, this.y, parentX, parentY);
  1643. grad.addColorStop(0, this.border.color);
  1644. grad.addColorStop(0.5, this.fillColor);
  1645. grad.addColorStop(1, this.border.color);
  1646. context.lineWidth = this.border.width;
  1647. context.strokeStyle = grad;
  1648. }
  1649. context.moveTo(this.x, this.y);
  1650. context.lineTo(parentX, parentY);
  1651. context.stroke();
  1652. var startRadians = Math.atan((parentY - this.y) / (parentX - this.x));
  1653. startRadians += ((parentX > this.x) ? -90 : 90) * Math.PI / 180;
  1654. _drawArrowhead(context, this.x, this.y, startRadians, 5, 8, this.fillColor);
  1655. if (this.border.active === true) {
  1656. _drawArrowhead(context, this.x, this.y, startRadians, 3, 5, this.border.color);
  1657. } else {
  1658. _drawArrowhead(context, this.x, this.y, startRadians, 3, 5, "white");
  1659. }
  1660. }
  1661. }, true);
  1662. },
  1663. /**
  1664. * Wraps any HTML element as a Raska element
  1665. *
  1666. * @class htmlElement
  1667. * @constructor
  1668. * @extends _basicElement
  1669. */
  1670. htmlElement: function (element) {
  1671. if (!_helpers.$obj.isValid(element)) {
  1672. var nullObj = new _defaultConfigurations.errors.nullParameterException("element");
  1673. console.error(nullObj.message);
  1674. throw nullObj;
  1675. }
  1676. var targetElement = element;
  1677. return _helpers.$obj.extend(new _basicElement(), {
  1678. name: "htmlElement" + _helpers.$obj.generateId(),
  1679. canHandleEvents: function () { return false; },
  1680. getType: function () { return _elementTypes.html; },
  1681. graphNode: false,
  1682. isSerializable: function () { return false; },
  1683. canLink: function () { return false; },
  1684. isLinkable: function () { return false; },
  1685. border: { active: false },
  1686. fillColor: "",
  1687. getWidth: function () { return element.clientWidth; },
  1688. getHeight: function () { return element.clientHeight; },
  1689. drawTo: function (canvas, context) { },
  1690. existsIn: function (x, y) { return false; },
  1691. handleInteractions: true,
  1692. onIteraction: function (iteractionType, trigger) {
  1693. var triggerWrapper = function (evt) {
  1694. trigger(evt, targetElement, iteractionType);
  1695. };
  1696. _helpers.$device.on(targetElement, iteractionType, triggerWrapper);
  1697. }
  1698. }, true);
  1699. },
  1700. /**
  1701. * Creates a triangle.
  1702. *
  1703. * @class triangle
  1704. * @extends _basicElement
  1705. */
  1706. triangle: function (pointingUp) {
  1707. var _bcData = {
  1708. p2: { x: 0, y: 0 },
  1709. p3: { x: 0, y: 0 }
  1710. };
  1711. return _helpers.$obj.extend(new _basicElement(), {
  1712. name: "triangle" + _helpers.$obj.generateId(),
  1713. border: { color: "gray", active: true, width: 2 },
  1714. getType: function () { return _elementTypes.triangle; },
  1715. fillColor: "silver",
  1716. pointingUp: (pointingUp !== false),
  1717. dimensions: {
  1718. width: 50,
  1719. height: 50
  1720. },
  1721. setWidth: function (width) {
  1722. this.dimensions.width = width;
  1723. return this;
  1724. },
  1725. getWidth: function () {
  1726. return this.dimensions.width;
  1727. },
  1728. setHeight: function (height) {
  1729. this.dimensions.height = height;
  1730. return this;
  1731. },
  1732. getAdjustedHeight: function () {
  1733. return (this.pointingUp === true) ? (this.dimensions.height * -1) : (this.dimensions.height / 2);
  1734. },
  1735. getHeight: function () {
  1736. return this.dimensions.height;
  1737. },
  1738. drawTo: function (canvas, context) {
  1739. var trasform = this.pointingUp === false ? function (x, y) { return x + y; } : function (x, y) { return x - y; },
  1740. coordinates = this.getAdjustedCoordinates();
  1741. context.beginPath();
  1742. context.fillStyle = this.fillColor;
  1743. context.moveTo(coordinates.x, coordinates.y);
  1744. context.lineTo(_bcData.p2.x = (coordinates.x + (this.dimensions.width / 2)),
  1745. _bcData.p2.y = trasform(coordinates.y, this.dimensions.height));
  1746. context.lineTo(_bcData.p3.x = coordinates.x + this.dimensions.width,
  1747. _bcData.p3.y = coordinates.y);
  1748. context.closePath();
  1749. if (this.border.active === true) {
  1750. context.lineWidth = this.border.width;
  1751. context.strokeStyle = this.border.color;
  1752. }
  1753. context.fill();
  1754. context.stroke();
  1755. },
  1756. existsIn: function (x, y) {
  1757. //https://en.wikipedia.org/wiki/Barycentric_coordinate_system_%28mathematics%29
  1758. var coordinates = this.getAdjustedCoordinates();
  1759. var p1 = coordinates, p2 = _bcData.p2, p3 = _bcData.p3, p = { x: x, y: y };
  1760. var alpha = ((p2.y - p3.y) * (p.x - p3.x) + (p3.x - p2.x) * (p.y - p3.y)) / ((p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y)),
  1761. beta = ((p3.y - p1.y) * (p.x - p3.x) + (p1.x - p3.x) * (p.y - p3.y)) / ((p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y)),
  1762. gamma = 1 - alpha - beta;
  1763. return alpha > 0 && beta > 0 && gamma > 0;
  1764. },
  1765. adjustPosition: function (newX, newY) {
  1766. var $parent = this.getParent();
  1767. if ((this.position === _positionTypes.relative) && ($parent !== null)) {
  1768. var adjustedParent = $parent.getAdjustedCoordinates();
  1769. var w = $parent.getWidth() / 2;
  1770. var h = $parent.getHeight() / 2;
  1771. var diffX = (newX - adjustedParent.x) - w;
  1772. var diffH = (newY - adjustedParent.y) - h;
  1773. this.x = diffX < 0 ? Math.max(diffX, w * -1) : Math.min(diffX, w);;
  1774. this.y = diffH < 0 ? Math.max(diffH, h * -1) : Math.min(diffH, h);
  1775. } else {
  1776. this.x = newX;
  1777. this.y = newY;
  1778. }
  1779. return this;
  1780. }
  1781. }, true);
  1782. },
  1783. /**
  1784. * Creates a cricle.
  1785. *
  1786. * @class circle
  1787. * @extends _basicElement
  1788. */
  1789. circle: function () {
  1790. return _helpers.$obj.extend(new _basicElement(), {
  1791. name: "circle" + _helpers.$obj.generateId(),
  1792. border: { color: "gray", active: true, width: 2 },
  1793. getType: function () { return _elementTypes.circle; },
  1794. fillColor: "silver",
  1795. radius: 20,
  1796. setWidth: function (r) {
  1797. this.radius = r / 2;
  1798. return this;
  1799. },
  1800. getWidth: function () {
  1801. return this.radius * 2;
  1802. },
  1803. getAdjustedWidth: function () {
  1804. return this.radius / 2;
  1805. },
  1806. setHeight: function (r) {
  1807. this.radius = r / 2;
  1808. return this;
  1809. },
  1810. getAdjustedHeight: function () {
  1811. return this.radius / 2;
  1812. },
  1813. getHeight: function () {
  1814. return this.radius * 2;
  1815. },
  1816. drawTo: function (canvas, context) {
  1817. var coordinates = this.getAdjustedCoordinates();
  1818. context.beginPath();
  1819. context.arc(coordinates.x, coordinates.y, this.radius, 0, 2 * Math.PI, false);
  1820. context.fillStyle = this.fillColor;
  1821. context.fill();
  1822. if (this.border.active === true) {
  1823. context.lineWidth = this.border.width;
  1824. context.strokeStyle = this.border.color;
  1825. context.stroke();
  1826. }
  1827. },
  1828. existsIn: function (x, y) {
  1829. var dx = this.x - x;
  1830. var dy = this.y - y;
  1831. return (dx * dx + dy * dy < this.radius * this.radius);
  1832. },
  1833. getBoundaries: function () {
  1834. var p = this.radius / 2;
  1835. return {
  1836. x: p,
  1837. y: p,
  1838. minX: p * -1,
  1839. minY: p * -1
  1840. };
  1841. },
  1842. adjustPosition: function (newX, newY) {
  1843. var $parent = this.getParent();
  1844. if ((this.position === _positionTypes.relative) && ($parent !== null)) {
  1845. var adjustedParent = $parent.getAdjustedCoordinates();
  1846. var parentBoundaries = $parent.getBoundaries();
  1847. var w = parentBoundaries.x;
  1848. var h = parentBoundaries.y;
  1849. var diffX = (newX - adjustedParent.x) - w;
  1850. var diffH = (newY - adjustedParent.y) - h;
  1851. this.x = diffX < 0 ? Math.max(diffX, parentBoundaries.minX) : Math.min(diffX, w);;
  1852. this.y = diffH < 0 ? Math.max(diffH, parentBoundaries.minY) : Math.min(diffH, h);
  1853. } else {
  1854. this.x = newX;
  1855. this.y = newY;
  1856. }
  1857. return this;
  1858. }
  1859. }, true);
  1860. }
  1861. },
  1862. /**
  1863. * Holds the value for the active configuration on this Raska instance
  1864. *
  1865. * @private
  1866. * @attribute _activeConfiguration
  1867. * @default null
  1868. * @module raska
  1869. */
  1870. _activeConfiguration = null,
  1871. _elementInteractionEventData = (function () {
  1872. var interactionTypes = { "click": 0, "mousemove": 1 },
  1873. createTriggerUsing = function (originalTrigger) {
  1874. return function (evt, targetElement, interactionType) {
  1875. originalTrigger({
  1876. x: _drawing.mouseHelper.getX(evt),
  1877. y: _drawing.mouseHelper.getY(evt),
  1878. details: {
  1879. interactionType: interactionType,
  1880. targetElement: targetElement
  1881. }
  1882. });
  1883. };
  1884. };
  1885. return {
  1886. getInteractionTypes: interactionTypes,
  1887. register: function (targetElement, iteractionType, trigger) {
  1888. if ((iteractionType in interactionTypes)
  1889. && _helpers.$obj.isValid(targetElement)
  1890. && (targetElement.handleInteractions === true)) {
  1891. targetElement.onIteraction(iteractionType, createTriggerUsing(trigger));
  1892. }
  1893. }
  1894. };
  1895. })(),
  1896. /**
  1897. * An utility container that holds all the drawing related implementations
  1898. *
  1899. * @class _drawing
  1900. * @module raska
  1901. * @readOnly
  1902. * @static
  1903. */
  1904. _drawing = (function () {
  1905. var
  1906. /**
  1907. * Holds the elements that are to be drawn to the target canvas
  1908. *
  1909. * @private
  1910. * @property _elements
  1911. * @type Array
  1912. * @default []
  1913. */
  1914. _elements = [],
  1915. /**
  1916. * Whether or not we have a timer running
  1917. *
  1918. * @private
  1919. * @property _timerRunning
  1920. * @type Bool
  1921. * @default false
  1922. */
  1923. _timerRunning = false,
  1924. /**
  1925. * The Canvas element we're targeting
  1926. *
  1927. * @private
  1928. * @property _canvas
  1929. * @type HTMLElement
  1930. * @default null
  1931. */
  1932. _canvas = null,
  1933. /**
  1934. * The Canvas element (wraped by a Raska _basicElement)
  1935. *
  1936. * @private
  1937. * @property _canvasElement
  1938. * @type _basicElement
  1939. * @default null
  1940. */
  1941. _canvasElement = null,
  1942. /**
  1943. * The 2dContext from the canvas element we're targeting
  1944. *
  1945. * @private
  1946. * @property _2dContext
  1947. * @type Object
  1948. * @default null
  1949. */
  1950. _2dContext = null,
  1951. /**
  1952. * Handles the periodic draw of the elements in this Raska instance
  1953. *
  1954. * @method _timedDrawing
  1955. * @private
  1956. */
  1957. _timedDrawing = function () {
  1958. if (_timerRunning === true) {
  1959. _draw();
  1960. w.requestAnimationFrame(_timedDrawing);
  1961. }
  1962. },
  1963. /**
  1964. * Plots the element itself and all its related nodes into the canvas
  1965. *
  1966. * @method _drawAllIn
  1967. * @param {_basicElement} element The element being drawn to the canvas
  1968. * @private
  1969. */
  1970. _drawAllIn = function (element) {
  1971. element.drawTo(_canvas, _2dContext);
  1972. var childElements = element.getChildElements();
  1973. for (var i = 0; i < childElements.length; i++) {
  1974. _drawAllIn(childElements[i]);
  1975. }
  1976. },
  1977. /**
  1978. * A utility that detects mouse's relative position on the canvas
  1979. *
  1980. * @class _mouse
  1981. * @for _drawing
  1982. * @static
  1983. */
  1984. _mouse = {
  1985. /**
  1986. * Gets mouses's X position relative to the current canvas
  1987. *
  1988. * @method getX
  1989. * @param {Event} evt Event we're bubbling in
  1990. * @for _mouse
  1991. * @return {Number} X value
  1992. * @static
  1993. */
  1994. getX: function (evt) {
  1995. return _helpers.$device.gathersXYPositionFrom(_canvas, evt).x;
  1996. },
  1997. /**
  1998. * Gets mouses's Y position relative to the current canvas
  1999. *
  2000. * @method getY
  2001. * @param {Event} evt Event we're bubbling in
  2002. * @for _mouse
  2003. * @return {Number} Y value
  2004. * @static
  2005. */
  2006. getY: function (evt) {
  2007. return _helpers.$device.gathersXYPositionFrom(_canvas, evt).y;
  2008. },
  2009. /**
  2010. * Gathers the mouse coordinates without the need of an event bubble
  2011. *
  2012. * @class staticCoordinates
  2013. * @for _mouse
  2014. * @static
  2015. */
  2016. staticCoordinates: (function () {
  2017. var _evt = { clientX: 0, clientY: 0 },
  2018. started = false;
  2019. return {
  2020. /**
  2021. * Gets mouses' X and Y positions relative to the current canvas
  2022. *
  2023. * @method getXY
  2024. * @return {Object} X and Y values
  2025. * @static
  2026. */
  2027. getXY: function () {
  2028. if (!started) {
  2029. _canvas.onmousemove = function (e) {
  2030. _evt.clientX = e.clientX;
  2031. _evt.clientY = e.clientY + 30;
  2032. }
  2033. started = true;
  2034. }
  2035. return {
  2036. x: _mouse.getX(_evt),
  2037. y: _mouse.getY(_evt)
  2038. };
  2039. }
  2040. };
  2041. })()
  2042. },
  2043. /**
  2044. * A utility that tracks the state of the element being draged (if any)
  2045. *
  2046. * @class _elementBeingDraged
  2047. * @for _drawing
  2048. */
  2049. _elementBeingDraged = {
  2050. /**
  2051. * The element being draged
  2052. *
  2053. * @property reference
  2054. * @type _basicElement
  2055. * @default null
  2056. * @for _elementBeingDraged
  2057. * @static
  2058. */
  2059. reference: null,
  2060. /**
  2061. * Whether or not the user is holding the CTRL key
  2062. *
  2063. * @property holdingCTRL
  2064. * @type Bool
  2065. * @default false
  2066. * @for _elementBeingDraged
  2067. * @static
  2068. */
  2069. holdingCTRL: false,
  2070. /**
  2071. * A copy from the original border specification for the element being draged
  2072. *
  2073. * @property originalBorder
  2074. * @type Object
  2075. * @default {}
  2076. * @for _elementBeingDraged
  2077. * @static
  2078. */
  2079. originalBorder: {},
  2080. /**
  2081. * The types of drag that can be applied to an element
  2082. *
  2083. * It can be either:
  2084. * 1 - moving
  2085. * 3 - linking
  2086. *
  2087. * @attribute dragTypes
  2088. */
  2089. dragTypes: {
  2090. /**
  2091. * The element is being moved
  2092. *
  2093. * @element moving
  2094. * @parents dragTypes
  2095. */
  2096. moving: 1,
  2097. /**
  2098. * The element is being linked
  2099. *
  2100. * @element moving
  2101. * @parents dragTypes
  2102. */
  2103. linking: 3
  2104. },
  2105. /**
  2106. * Default drag type
  2107. *
  2108. * @property dragType
  2109. * @type Number
  2110. * @default 0
  2111. * @for _elementBeingDraged
  2112. * @static
  2113. */
  2114. dragType: 0,
  2115. temporaryLinkArrow: (function () {
  2116. return {
  2117. /**
  2118. * Whenever possible/necessary it plots the linking arrow to the canvas
  2119. *
  2120. * @method tryRender
  2121. * @private
  2122. */
  2123. tryRender: function () {
  2124. if ((_elementBeingDraged.reference !== null)
  2125. && (_elementBeingDraged.dragType === _elementBeingDraged.dragTypes.linking)
  2126. && _elementBeingDraged.reference.isLinkable() === true) {
  2127. var targetXY = _mouse.staticCoordinates.getXY(),
  2128. arrow = new _defaultConfigurations.arrow({
  2129. x: targetXY.x,
  2130. y: targetXY.y - 30,
  2131. getHeight: function () { return 0; }
  2132. });
  2133. arrow.name = "_temp";
  2134. arrow.setParent(_elementBeingDraged.reference);
  2135. arrow.drawTo(_canvas, _2dContext);
  2136. }
  2137. return this;
  2138. }
  2139. };
  2140. })(),
  2141. border: {
  2142. whenMoving: {
  2143. color: "yellow",
  2144. active: true
  2145. },
  2146. whenLiking: {
  2147. color: "green",
  2148. active: true
  2149. }
  2150. }
  2151. },
  2152. /**
  2153. * Handles de mouse move envent
  2154. *
  2155. * @method _whenMouseMove
  2156. * @param {Event} evt Event we're bubbling in
  2157. * @for _drawing
  2158. * @private
  2159. */
  2160. _whenMouseMove = function (evt) {
  2161. if ((_elementBeingDraged.reference !== null)
  2162. && (_elementBeingDraged.dragType === _elementBeingDraged.dragTypes.moving)) {
  2163. _helpers.$log.info("Moving element", _elementBeingDraged);
  2164. _elementBeingDraged.reference.x = _mouse.getX(evt);
  2165. _elementBeingDraged.reference.y = _mouse.getY(evt);
  2166. }
  2167. },
  2168. /**
  2169. * Handles de mouse up envent
  2170. *
  2171. * @method _whenMouseUp
  2172. * @param {Event} evt Event we're bubbling in
  2173. * @private
  2174. */
  2175. _whenMouseUp = function (evt) {
  2176. if (_elementBeingDraged.reference !== null) {
  2177. if (_elementBeingDraged.dragType === _elementBeingDraged.dragTypes.linking) {
  2178. for (var i = 0; i < _elements.length; i++) {
  2179. if (_elements[i] !== _elementBeingDraged.reference
  2180. && _elements[i].existsIn(_mouse.getX(evt), _mouse.getY(evt))) {
  2181. if (_elementBeingDraged.reference.addLinkTo(_elements[i])) {
  2182. _elements.push(new _defaultConfigurations
  2183. .arrow(_elements[i])
  2184. .setParent(_elementBeingDraged.reference));
  2185. break;
  2186. }
  2187. }
  2188. }
  2189. }
  2190. _elementBeingDraged.reference.on.release(_mouse.getX(evt), _mouse.getY(evt),
  2191. _elementBeingDraged.reference, evt);
  2192. _elementBeingDraged.reference.border = _elementBeingDraged.originalBorder;
  2193. _elementBeingDraged.reference = null;
  2194. }
  2195. },
  2196. /**
  2197. * Removes a given element from the canvas
  2198. *
  2199. * @method _removeElement
  2200. * @param {_basicElement} e Element that is to be removed
  2201. * @private
  2202. * @chainable
  2203. */
  2204. _removeElement = function (e) {
  2205. _helpers.$log.info("Removing element", e);
  2206. e.disable();
  2207. _elements.splice(_elements.indexOf(e), 1);
  2208. return this;
  2209. },
  2210. /**
  2211. * Adds a given element from the canvas
  2212. *
  2213. * @method _addElement
  2214. * @param {_basicElement} e Element that is to be added
  2215. * @private
  2216. * @chainable
  2217. */
  2218. _addElement = function (e) {
  2219. _helpers.$log.info("Adding element", e);
  2220. _elements.push(e);
  2221. return this;
  2222. },
  2223. /**
  2224. * Handles de key up envent
  2225. *
  2226. * @method _whenKeyUp
  2227. * @param {Event} evt Event we're bubbling in
  2228. * @private
  2229. * @chainable
  2230. */
  2231. _whenKeyUp = function (evt) {
  2232. _elementBeingDraged.holdingCTRL = false;
  2233. return this;
  2234. },
  2235. /**
  2236. * Handles de key down envent
  2237. *
  2238. * @method _whenKeyDown
  2239. * @param {Event} evt Event we're bubbling in
  2240. * @private
  2241. * @chainable
  2242. */
  2243. _whenKeyDown = function (e) {
  2244. var key = e.keyCode || e.charCode;
  2245. if (((key === 46) || (key === 8))
  2246. && (_elementBeingDraged.reference !== null)) {
  2247. _removeElement(_elementBeingDraged.reference.clearAllLinks());
  2248. _elementBeingDraged.reference = null;
  2249. } else {
  2250. _elementBeingDraged.holdingCTRL = (key === 17);
  2251. }
  2252. return this;
  2253. },
  2254. /**
  2255. * Handles the click event
  2256. *
  2257. * @method _checkClick
  2258. * @param {Event} evt Event we're bubbling in
  2259. * @private
  2260. */
  2261. _checkClick = function (evt) {
  2262. evt = evt || w.event;
  2263. for (var i = 0; i < _elements.length; i++) {
  2264. if (_elements[i].existsIn(_mouse.getX(evt), _mouse.getY(evt))) {
  2265. if ((_elementBeingDraged.reference = _elements[i]).canHandleEvents()) {
  2266. _elementBeingDraged.reference.on.click(
  2267. _mouse.getX(evt),
  2268. _mouse.getY(evt),
  2269. _elementBeingDraged.reference, evt);
  2270. }
  2271. }
  2272. }
  2273. if (_elementBeingDraged.reference !== null) {
  2274. var dragType = _elementBeingDraged.dragTypes.moving;
  2275. if (!_helpers.$device.isTouch) {
  2276. dragType = _elementBeingDraged.holdingCTRL === true && _elementBeingDraged.reference.isLinkable() === true ?
  2277. _elementBeingDraged.dragTypes.linking :
  2278. evt.which;
  2279. }
  2280. _elementBeingDraged.originalBorder = _elementBeingDraged.reference.border;
  2281. _elementBeingDraged.reference.border =
  2282. ((_elementBeingDraged.dragType = dragType) === _elementBeingDraged.dragTypes.moving) ?
  2283. _elementBeingDraged.border.whenMoving :
  2284. _elementBeingDraged.border.whenLiking;
  2285. _helpers.$log.info("Dragging element", { r: _elementBeingDraged.reference, d: dragType });
  2286. }
  2287. if (evt.preventDefault) {
  2288. evt.preventDefault();
  2289. } else if (evt.returnValue) {
  2290. evt.returnValue = false;
  2291. }
  2292. return false;
  2293. },
  2294. /**
  2295. * Plots each and every element to the canvas and registers all the event handlers delegates
  2296. *
  2297. * @method _draw
  2298. * @private
  2299. * @chainable
  2300. */
  2301. _draw = function () {
  2302. if (_canvas === null) {
  2303. _2dContext = (_canvas = $("#" + _activeConfiguration.targetCanvasId))
  2304. .getContext("2d");
  2305. /// Canvas related events
  2306. _helpers.$dom.get(_canvas)
  2307. .on("mousedown", _checkClick)
  2308. .on("mousemove", _whenMouseMove)
  2309. .on("contextmenu", function (e) {
  2310. e.preventDefault();
  2311. return false;
  2312. });
  2313. if (_helpers.$obj.isArray(_activeConfiguration.toolboxButtons)) {
  2314. var wCanvas = _helpers.$dom.get(_canvas);
  2315. if (wCanvas.getParent().attr("id") !== _activeConfiguration.targetCanvasContainerId) {
  2316. wCanvas
  2317. .getParent()
  2318. .addChild("div")
  2319. .attr("id", _activeConfiguration.targetCanvasContainerId)
  2320. .addChild(wCanvas.raw());
  2321. }
  2322. _helpers.$obj.forEach(_activeConfiguration.toolboxButtons, function (button) {
  2323. wCanvas
  2324. .addSibling("div").attr("id", button.id = _helpers.$obj.generateId())
  2325. .html(button.template)
  2326. .on("click", function () {
  2327. button.onclick(_canvas);
  2328. });
  2329. });
  2330. }
  2331. /// Window events
  2332. _helpers.$dom.get(w)
  2333. .on("mouseup", _whenMouseUp)
  2334. .on("keydown", _whenKeyDown)
  2335. .on("keyup", _whenKeyUp);
  2336. if (_helpers.$device.isTouch === true) {
  2337. /// If we're in a touch device
  2338. _helpers.$dom.get(_canvas)
  2339. .on("touchstart", _checkClick)
  2340. .on("touchmove", _whenMouseMove);
  2341. _helpers.$dom.get(w)
  2342. .on("touchend", _whenMouseUp)
  2343. .on("touchcancel", _whenMouseUp);
  2344. }
  2345. _canvasElement = _helpers.$obj.extend(new _defaultConfigurations.htmlElement(_canvas), {});
  2346. }
  2347. _2dContext.clearRect(0, 0, _canvas.width, _canvas.height);
  2348. for (var i = 0; i < _elements.length; i++) {
  2349. _drawAllIn(_elements[i]);
  2350. }
  2351. _elementBeingDraged.temporaryLinkArrow.tryRender();
  2352. return this;
  2353. },
  2354. /**
  2355. * Gathers all elements being ploted to the canvas and organizes it as a directed graph JSON
  2356. *
  2357. * @method _getElementsSlim
  2358. * @return {_graphNodeInfo} Graph node information
  2359. * @private
  2360. */
  2361. _getElementsSlim = function () {
  2362. var result = {},
  2363. element,
  2364. links;
  2365. for (var i = 0; i < _elements.length; i++) {
  2366. element = _elements[i];
  2367. if (element.graphNode === true) {
  2368. if ((((links = element.getLinksFrom()) === null) || (links.length === 0))
  2369. && (((links = element.getLinksTo()) === null) || (links.length === 0))) {
  2370. throw new _defaultConfigurations.errors.elementDoesNotHaveALink(element.name);
  2371. }
  2372. result[element.name] = new _graphNodeInfo(element, null, element.getLinksTo());
  2373. }
  2374. }
  2375. return result;
  2376. };
  2377. return {
  2378. mouseHelper: _mouse,
  2379. /**
  2380. * Add a given element to the drawing elements array
  2381. *
  2382. * @method addElement
  2383. * @param {_basicElement} element The element to be drawn
  2384. * @chainable
  2385. */
  2386. addElement: function (element) {
  2387. _addElement(element);
  2388. return this;
  2389. },
  2390. /**
  2391. * Gets the canvas' dataURL
  2392. *
  2393. * @method getDataUrl
  2394. * @param {bool} cropImage Whether or not to crop the exported image
  2395. */
  2396. getDataUrl: function (cropImage) {
  2397. if (cropImage === true) {
  2398. var silkCanvas = _helpers.$dom.create("canvas").raw(),
  2399. silkCanvasContext = silkCanvas.getContext("2d"),
  2400. sorter = function (a, b) { return a - b },
  2401. sourceCanvasWidth = _canvas.width,
  2402. sourceCanvasHeight = _canvas.height,
  2403. pix = { x: [], y: [] },
  2404. imageData = _2dContext.getImageData(0, 0, sourceCanvasWidth, sourceCanvasHeight),
  2405. index = 0;
  2406. for (var y = 0; y < sourceCanvasHeight; y++) {
  2407. for (var x = 0; x < sourceCanvasWidth; x++) {
  2408. index = (y * sourceCanvasWidth + x) * 4;
  2409. if (imageData.data[index + 3] > 0) {
  2410. pix.x.push(x);
  2411. pix.y.push(y);
  2412. }
  2413. }
  2414. }
  2415. pix.x.sort(sorter);
  2416. pix.y.sort(sorter);
  2417. var n = pix.x.length - 1;
  2418. sourceCanvasWidth = pix.x[n] - pix.x[0];
  2419. sourceCanvasHeight = pix.y[n] - pix.y[0];
  2420. var cut = _2dContext.getImageData(pix.x[0], pix.y[0], sourceCanvasWidth, sourceCanvasHeight);
  2421. silkCanvas.width = sourceCanvasWidth;
  2422. silkCanvas.height = sourceCanvasHeight;
  2423. silkCanvasContext.putImageData(cut, 0, 0);
  2424. return silkCanvas.toDataURL();
  2425. }
  2426. return _canvas.toDataURL();
  2427. },
  2428. /**
  2429. * Removes a given element from the canvas
  2430. *
  2431. * @method remove
  2432. * @param {_basicElement} element Element that is to be removed
  2433. * @private
  2434. * @chainable
  2435. */
  2436. remove: function (element) {
  2437. _removeElement(element);
  2438. return this;
  2439. },
  2440. /**
  2441. * Gathers all elements being ploted to the canvas and organizes it as a directed graph JSON
  2442. *
  2443. * @method getElementsSlim
  2444. * @return {_graphNodeInfo} Graph node information
  2445. */
  2446. getElementsSlim: function () {
  2447. return _getElementsSlim();
  2448. },
  2449. /**
  2450. * Gathers all elements being ploted to the canvas
  2451. *
  2452. * @method getElements
  2453. * @return {_basicElement[]} Graph node information
  2454. */
  2455. getElements: function () {
  2456. return _elements;
  2457. },
  2458. /**
  2459. * Redefines the elements that are supposed to be ploted to the canvas
  2460. *
  2461. * @method reloadUsing
  2462. * @param {_basicElements[]} elements The elements that are going to be ploted
  2463. * @static
  2464. * @chainable
  2465. */
  2466. reloadUsing: function (elements) {
  2467. _elements = elements;
  2468. this.initializeTimedDrawing();
  2469. },
  2470. /**
  2471. * Returns the corresponding Raska Canvas element
  2472. *
  2473. * @method getCanvasElement
  2474. */
  2475. getCanvasElement: function () {
  2476. return _canvasElement;
  2477. },
  2478. /**
  2479. * Returns the corresponding Raska Canvas element
  2480. *
  2481. * @method getCanvasElement
  2482. * @return {HTMLElement} Canvas
  2483. */
  2484. getCanvas: function () {
  2485. return _canvas;
  2486. },
  2487. /**
  2488. * Initializes the drawing process
  2489. *
  2490. * @method initializeTimedDrawing
  2491. */
  2492. initializeTimedDrawing: function () {
  2493. if (_timerRunning === false) {
  2494. _timerRunning = true;
  2495. _timedDrawing();
  2496. }
  2497. }
  2498. };
  2499. })(),
  2500. /**
  2501. * All public avaliable methods from the Raska library
  2502. *
  2503. * @class _public
  2504. */
  2505. _public = {
  2506. /**
  2507. * Adds a new Label to the target canvas
  2508. *
  2509. * @method newLabel
  2510. * @return {_defaultConfigurations.label} Copy of '_defaultConfigurations.label' object
  2511. * @static
  2512. */
  2513. newLabel: function (defaultConfiguration) {
  2514. return _helpers.$obj.extend(new _defaultConfigurations.label(), defaultConfiguration);
  2515. },
  2516. /**
  2517. * Adds a new Square to the target canvas
  2518. *
  2519. * @method newSquare
  2520. * @return {_defaultConfigurations.square} Copy of '_defaultConfigurations.square' object
  2521. * @static
  2522. */
  2523. newSquare: function () {
  2524. return _helpers.$obj.extend(new _defaultConfigurations.square(), {});
  2525. },
  2526. /**
  2527. * Adds a new Triangle to the target canvas
  2528. *
  2529. * @method newTriangle
  2530. * @return {_defaultConfigurations.square} Copy of '_defaultConfigurations.triangle' object
  2531. * @static
  2532. */
  2533. newTriangle: function (pointingUp) {
  2534. return _helpers.$obj.extend(new _defaultConfigurations.triangle(pointingUp), {});
  2535. },
  2536. /**
  2537. * Adds a new Circle to the target canvas
  2538. *
  2539. * @method newCircle
  2540. * @return {_defaultConfigurations.circle} Copy of '_defaultConfigurations.circle' object
  2541. * @static
  2542. */
  2543. newCircle: function () {
  2544. return _helpers.$obj.extend(new _defaultConfigurations.circle(), {});
  2545. },
  2546. /**
  2547. * Plots an element to the canvas
  2548. *
  2549. * @method plot
  2550. * @return {_public} Reference to the '_public' pointer
  2551. * @static
  2552. * @chainable
  2553. */
  2554. plot: function (element) {
  2555. _drawing.addElement(element);
  2556. return _public;
  2557. },
  2558. /**
  2559. * Exports current canvas data as an image to a new window
  2560. *
  2561. * @method exportImage
  2562. * @param {bool} cropImage Whether or not to crop the exported image
  2563. * @return {_public} Reference to the '_public' pointer
  2564. * @chainable
  2565. * @static
  2566. */
  2567. exportImage: function (cropImage) {
  2568. w.open(_drawing.getDataUrl(cropImage), '_blank');
  2569. return _public;
  2570. },
  2571. /**
  2572. * Retrieves the raw elements from the drawing stack
  2573. *
  2574. * @method getElementsRaw
  2575. * @return {json} The JSON object that represents ALL the Raska elements in the canvas
  2576. * @static
  2577. */
  2578. getElementsRaw: function () {
  2579. return _drawing.getElements();
  2580. },
  2581. /**
  2582. * Retrieves the directed graph represented by the elements in the canvas
  2583. *
  2584. * @method getElementsSlim
  2585. * @return {json} The JSON object that represents the current directed graph drawn to the canvas
  2586. * @static
  2587. */
  2588. getElementsSlim: function () {
  2589. return _drawing.getElementsSlim();
  2590. },
  2591. /**
  2592. * Retrieves the ENTIRE directed graph represented by the elements in the canvas
  2593. *
  2594. * @method getElementsData
  2595. * @return {String} The stringfied JSON that represents the current directed graph drawn to the canvas
  2596. * @static
  2597. */
  2598. getElementsString: function () {
  2599. return _helpers.$obj.deconstruct(_drawing.getElements());
  2600. },
  2601. /**
  2602. * Redefines the elements that are supposed to be ploted to the canvas
  2603. *
  2604. * @method loadElementsFrom
  2605. * @param {_basicElements[]} source The elements that are going to be ploted
  2606. * @static
  2607. * @chainable
  2608. */
  2609. loadElementsFrom: function (source) {
  2610. var preparsedSource = JSON.parse(source);
  2611. if (_helpers.$obj.isArray(preparsedSource)) {
  2612. var realSource = [], childElements = [], i = 0, parsed = null;
  2613. /// Create a basic element instance
  2614. for (i = 0; i < preparsedSource.length; i++) {
  2615. if ((parsed = _helpers.$obj.recreate(preparsedSource[i])) === null) {
  2616. alert("Invalid JSON. See the console for more info");
  2617. console.error("Could not deserialize this element", preparsedSource[i]);
  2618. return this;
  2619. }
  2620. if (preparsedSource[i].parent === null) {
  2621. parsed.originalIndex = i;
  2622. realSource.push(parsed);
  2623. } else {
  2624. childElements.push(parsed);
  2625. }
  2626. }
  2627. /// Adds back the links between elements
  2628. var findElement = function (itemName) {
  2629. if (!_helpers.$obj.isValid(itemName)) {
  2630. return null;
  2631. }
  2632. function find(arr) {
  2633. for (var j = 0; j < arr.length; j++) {
  2634. if (arr[j].name === itemName) {
  2635. return arr[j];
  2636. }
  2637. }
  2638. }
  2639. var itemFound = find(realSource) || find(childElements);
  2640. if (!itemFound) {
  2641. throw _defaultConfigurations.errors.itemNotFoundException(itemName);
  2642. }
  2643. return itemFound;
  2644. };
  2645. for (i = 0; i < realSource.length; i++) {
  2646. _helpers.$obj.recreateLinks(realSource[i], preparsedSource[realSource[i].originalIndex], findElement);
  2647. delete realSource[i].originalIndex;
  2648. }
  2649. /// Recriates all arrows
  2650. var linksTo = [], item;
  2651. for (i = 0; i < realSource.length; i++) {
  2652. item = realSource[i];
  2653. if ((item.isLinkable() === true)
  2654. && ((linksTo = item.getLinksTo()).length > 0)) {
  2655. for (var k = 0; k < linksTo.length; k++) {
  2656. realSource.push(new _defaultConfigurations
  2657. .arrow(linksTo[k])
  2658. .setParent(item));
  2659. }
  2660. }
  2661. }
  2662. _drawing.reloadUsing(realSource);
  2663. }
  2664. return this;
  2665. },
  2666. /**
  2667. * Toggles fullscreen mode on/off
  2668. *
  2669. * @property toggleFullScreen
  2670. * @static
  2671. * @chainable
  2672. */
  2673. toggleFullScreen: function () {
  2674. _canvasController.toggleFullScreen(_drawing.getCanvas());
  2675. return _public;
  2676. },
  2677. /**
  2678. * Retrieves all possible position types
  2679. *
  2680. * @property positionTypes
  2681. * @type _positionTypes
  2682. * @static
  2683. */
  2684. positionTypes: (function () {
  2685. return _positionTypes;
  2686. })(),
  2687. /**
  2688. * Configures the Raska library to target a given canvas using the configuration passed
  2689. * as a parameter
  2690. *
  2691. * @method installUsing
  2692. * @param {_defaultConfigurations.library} configuration Configuration data that should be used to configure this Raska instance
  2693. * @return {_public} Reference to the '_public' pointer
  2694. * @static
  2695. * @chainable
  2696. */
  2697. installUsing: function (configuration) {
  2698. _activeConfiguration = _helpers.$obj.extend(_defaultConfigurations.library, configuration);
  2699. _drawing.initializeTimedDrawing();
  2700. return _public;
  2701. },
  2702. /**
  2703. * Registers a handler to be trigered by any interaction taken place against the canvas
  2704. *
  2705. * @method onElementInteraction
  2706. * @param {string} iteractionType When to trigger the iteraction handler
  2707. * @param {Function} trigger What to do whenever an element iteraction happens
  2708. * @static
  2709. * @chainable
  2710. */
  2711. onCanvasInteraction: function (iteractionType, trigger) {
  2712. _elementInteractionEventData.register(_drawing.getCanvasElement(), iteractionType, trigger);
  2713. return this;
  2714. },
  2715. /**
  2716. * Checks whether or not an element does exists at a given coordinate
  2717. *
  2718. * @method checkCollisionOn
  2719. * @param {number} x X position
  2720. * @param {number} y Y position
  2721. * @return {bool} Wheter or not an element can be found at these coordinates
  2722. * @static
  2723. */
  2724. checkCollisionOn: function (x, y) {
  2725. return _public.tryGetElementOn(x, y) !== null;
  2726. },
  2727. /**
  2728. * Tries to get the element that exists at a given coordinate
  2729. *
  2730. * @method tryGetElementOn
  2731. * @param {number} x X position
  2732. * @param {number} y Y position
  2733. * @return {_basicElement} Raska basic element (if any) or null
  2734. * @static
  2735. */
  2736. tryGetElementOn: function (x, y) {
  2737. var elements = _drawing.getElements();
  2738. for (var i = 0; i < elements.length; i++) {
  2739. if (elements[i].existsIn(x, y) === true) {
  2740. return elements[i];
  2741. }
  2742. }
  2743. return null;
  2744. },
  2745. /**
  2746. * Removes a given element from the canvas
  2747. *
  2748. * @method remove
  2749. * @param {_basicElement} element Element that is to be removed
  2750. * @private
  2751. * @chainable
  2752. */
  2753. remove: function (element) {
  2754. _drawing.remove(element);
  2755. return this;
  2756. },
  2757. /**
  2758. * Gathers the target canvas boundaries
  2759. *
  2760. * @method getCanvasBoundaries
  2761. * @static
  2762. * @return {maxW:number, maxH:number}
  2763. */
  2764. getCanvasBoundaries: function () {
  2765. var el = _drawing.getCanvasElement();
  2766. return { maxH: el.getHeight(), maxW: el.getWidth() };
  2767. },
  2768. /**
  2769. * Clears all elements from the canvas
  2770. *
  2771. * @method clear
  2772. * @static
  2773. * @chainable
  2774. */
  2775. clear: function () {
  2776. _drawing.reloadUsing([]);
  2777. return this;
  2778. },
  2779. $$: { $h: _helpers, $q: $, $c: _activeConfiguration }
  2780. };
  2781. w.raska = _public;
  2782. })(window, document);