// declare global metadata host if (!window.shapeTracingMetadataHost) { window.shapeTracingMetadataHost = {}; window.shapeTracingMetadataHost.placement = { 'n/a': 'n/a' }; window.shapeTracingMetadataHost.references = {}; } jQuery(function ($) { // default shape window height when first opened var defaultHeight = 200; // preload main objects var shapeTracingContainer = $('#shape-tracing-container'); var shapeTracingResizeHandle = $('#shape-tracing-resize-handle'); var shapeTracingToolbar = $('#shape-tracing-toolbar'); var shapeTracingToolbarSwitch = $('#shape-tracing-toolbar-switch'); var shapeTracingWindow = $('#shape-tracing-window'); var shapeTracingWindowTree = $('#shape-tracing-window-tree'); var shapeTracingWindowContent = $('#shape-tracing-window-content'); var shapeTracingGhost = $('#shape-tracing-container-ghost'); var shapeTracingOverlay = $('#shape-tracing-overlay'); var shapeTracingTabs = $('#shape-tracing-tabs'); var shapeTracingTabsShape = $('#shape-tracing-tabs-shape'); var shapeTracingTabsModel = $('#shape-tracing-tabs-model'); var shapeTracingTabsPlacement = $('#shape-tracing-tabs-placement'); var shapeTracingTabsTemplate = $('#shape-tracing-tabs-template'); var shapeTracingTabsHtml = $('#shape-tracing-tabs-html'); var shapeTracingBreadcrumb = $('#shape-tracing-breadcrumb'); var shapeTracingMetaContent = $('#shape-tracing-meta-content'); var shapeTracingEnabled = false; // store the size of the container when it is closed (default in css) var initialContainerSize = shapeTracingContainer.height(); var previousSize = 0; // represents the arrow to add to any collpasible container var glyph = $(''); // ensure the ghost has always the same size as the container // and the container is always positionned correctly var syncResize = function () { var _window = $(window); var containerHeight = shapeTracingContainer.outerHeight(); var containerWidth = shapeTracingContainer.outerWidth(); var toolbarHeight = shapeTracingToolbar.outerHeight(); var resizeHandleHeight = shapeTracingResizeHandle.outerHeight(); shapeTracingGhost.height(containerHeight); var windowHeight = _window.height(); var scrollTop = _window.scrollTop(); var containerWindowHeight = containerHeight - toolbarHeight - resizeHandleHeight; shapeTracingContainer.offset({ top: windowHeight - containerHeight + scrollTop, left: 0 }); shapeTracingWindow.height(containerWindowHeight); shapeTracingWindowTree.height(containerWindowHeight); shapeTracingWindowContent.height(containerWindowHeight); shapeTracingContainer.width('100%'); syncResizeMeta(); }; // forces the content meta zone's height to enable scrollbar var syncResizeMeta = function () { var containerHeight = shapeTracingContainer.outerHeight(); var containerWidth = shapeTracingContainer.outerWidth(); var toolbarHeight = shapeTracingToolbar.outerHeight(); var resizeHandleHeight = shapeTracingResizeHandle.outerHeight(); var tabsHeight = shapeTracingTabs.outerHeight(); var breadcrumbHeight = shapeTracingBreadcrumb.outerHeight(); if (tabsHeight) { padding = parseInt(shapeTracingMetaContent.css('padding-bottom') + shapeTracingMetaContent.css('padding-top')); shapeTracingMetaContent.height(containerHeight - toolbarHeight - resizeHandleHeight - tabsHeight - breadcrumbHeight - padding); } }; // ensure the size/position is correct whenver the container or the browser is resized shapeTracingContainer.resize(syncResize); $(window).resize(syncResize); $(window).resize(); // removes the position flickering by hiding it first, then showing when ready shapeTracingContainer.show(); // expand/collapse behavior // ensure the container has always a valid size when expanded shapeTracingToolbarSwitch.click(function () { var _this = $(this); _this.toggleClass('expanded'); if (_this.hasClass('expanded')) { shapeTracingContainer.height(Math.max(previousSize, defaultHeight, shapeTracingContainer.height())); enableShapeTracing(); } else { // save previous height previousSize = shapeTracingContainer.height(); shapeTracingContainer.height(initialContainerSize); disableShapeTracing(); } syncResize(); }); var disableShapeTracing = function () { shapeTracingEnabled = false; selectShape(); } var enableShapeTracing = function () { shapeTracingEnabled = true; } // add a resizable handle to the container $('#shape-tracing-resize-handle').addClass('ui-resizable-handle ui-resizable-n'); shapeTracingContainer.resizable({ handles: { n: '#shape-tracing-resize-handle' }, grid: 20, // mitigates the number of calls to syncResize(), and aligns to the line height resize: function () { shapeTracingEnabled = false }, stop: function () { shapeTracingEnabled = true } }); var shapeNodes = {}; // represents the main index of shape nodes, indexed by id // projects the shape ids to each DOM element var startShapeTracingBeacons = $('.shape-tracing-wrapper[shape-id]'); startShapeTracingBeacons.each(function () { var _this = $(this); var shapeNode = { id: _this.attr('shape-id'), type: _this.attr('shape-type'), hint: _this.attr('shape-hint'), parent: null, children: {} }; // register the new shape node into the main shape nodes index shapeNodes[shapeNode.id] = shapeNode; // assign the shape-id attribute to all direct children, except wrappers themselves (it would erase their own shape-id) var found = false; _this .nextUntil('[end-of="' + shapeNode.id + '"]') // all elements between the script beacons .find(':not(.shape-tracing-wrapper)') // all children but not inner beacons .andSelf() // add the first level items .attr('shape-id', shapeNode.id) // add the shape-id attribute .each(function () { // assign a shapeNode instance to the DOM element this.shapeNode = shapeNode; found = true; }); // if the shape is empty, add a hint if (!found) { shapeNode.hint = 'empty'; } this.shapeNode = shapeNode; }); // construct the shape tree based on all current nodes // for each start beacon, search for the first parent beacon, and create nodes if they don't exist startShapeTracingBeacons.each(function () { var _this = $(this); var shapeNode = this.shapeNode; var parent = _this.parents('[shape-id!=' + shapeNode.id + ']').get(0); shapeNodes[shapeNode.id] = shapeNode; if (parent.shapeNode) { var parentShapeNode = parent.shapeNode; shapeNodes[parentShapeNode.id] = parentShapeNode; shapeNode.parent = parentShapeNode; parentShapeNode.children[shapeNode.id] = shapeNode; } }); // removes all beacons as we don't need them anymore $('.shape-tracing-wrapper').remove(); // add first level shapes tree nodes var shapes = $(''); for (var shapeId in shapeNodes) { if (!shapeNodes[shapeId].parent) { shapes.append(createTreeNode(shapeNodes[shapeId])); } } shapeTracingWindowTree.append(shapes); // add the expand/collapse logic to the shapes tree shapeTracingWindowTree.find('li:has(ul:has(li))').prepend(glyph); // collapse all sub uls shapeTracingWindowTree.find('ul ul').toggle(false); // expands a list of shapes in the tree var openExpando = function (expando) { if (expando.hasClass("closed") || expando.hasClass("closing")) { expando.siblings('ul').slideDown(100, function () { expando.removeClass("opening").removeClass("closed").addClass("open"); }); expando.addClass("opening"); } } // collapses a list of shapes in the tree var closeExpando = function (expando) { if (!expando.hasClass("closed") && !expando.hasClass("closing")) { expando.siblings('ul').slideUp(100, function () { expando.removeClass("closing").removeClass("open").addClass("closed"); }); expando.addClass("closing"); } } //create an overlay on shapes' descendants var overlayTarget = null; $('[shape-id]').add(shapeTracingOverlay).mousemove( function (event) { if (!shapeTracingEnabled) { return; } event.stopPropagation(); if ($(this).get(0) == shapeTracingOverlay.get(0)) { shapeTracingOverlay.hide(); } var element = document.elementFromPoint(event.pageX - $(window).scrollLeft(), event.pageY - $(window).scrollTop()); shapeTracingOverlay.show(); while (element && !element.shapeNode) element = element.parentNode; if (!element || (overlayTarget != null && overlayTarget.get(0) == element)) { return; } element = $(element); shapeTracingOverlay.offset(element.offset()); shapeTracingOverlay.width(element.outerWidth()); // include border and padding shapeTracingOverlay.height(element.outerHeight()); // include border and padding overlayTarget = element; } ); var currentShape; // selects a specific shape in the tree, highlight its elements, and display the current tab var selectShape = function (shapeId) { // remove current tab content shapeTracingMetaContent.children().remove(); // remove selection ? if (!shapeId) { currentShape = null; shapeTracingOverlay.hide(); $('.shape-tracing-selected').removeClass('shape-tracing-selected'); shapeTracingWindowTree.find('.shape-tracing-selected').removeClass('shape-tracing-selected'); return; } currentShape = shapeId; $('.shape-tracing-selected').removeClass('shape-tracing-selected'); $('li[tree-shape-id="' + shapeId + '"] > div').add('[shape-id="' + shapeId + '"]').addClass('shape-tracing-selected'); shapeTracingOverlay.hide(); defaultTab(); } // select shapes when clicked shapeTracingOverlay.click(function () { var shapeNode = overlayTarget.get(0).shapeNode; selectShape(shapeNode.id); var lastExpanded = null; // open the tree until the selected element $('li[tree-shape-id="' + shapeNode.id + '"]').parents('li').andSelf().find('> .expando-glyph-container').each(function () { openExpando($(this)); }).each(function () { shapeTracingWindowTree.scrollTo(this, 0, { margin: true }); }); return false; }); //create an overlay on shape tree nodes shapeTracingWindowTree.find('[tree-shape-id] > div') .hover( function () { var _this = $(this); $('.shape-tracing-overlay').removeClass('shape-tracing-overlay'); _this.addClass('shape-tracing-overlay'); }, function () { $('.shape-tracing-overlay').removeClass('shape-tracing-overlay'); }) .click(function (event) { var shapeId = $(this).parent().get(0).shapeNode.id; selectShape(shapeId); var element = $('[shape-id="' + shapeId + '"]').get(0); // there might be no DOM element if the shape was empty, or is not displayed if (element) { $(window).scrollTo(element, 500, { margin: true }); } event.stopPropagation(); }); // move all shape tracing meta blocks to the content window $("[shape-id-meta]").detach().prependTo(shapeTracingWindowContent); // add the expand/collapse logic to the shape model shapeTracingWindowContent.find('li:has(ul:has(li))').prepend(glyph); // collapse all sub uls shapeTracingWindowContent.find('ul ul').toggle(false); // Shape tab var displayTabShape = function () { // toggle the selected class shapeTracingTabs.children('.selected').removeClass('selected'); shapeTracingTabsShape.addClass('selected'); // remove old content shapeTracingMetaContent.empty(); // render the template if (currentShape && shapeTracingMetadataHost[currentShape]) { $("#shape-tracing-tabs-shape-template").tmpl(shapeTracingMetadataHost[currentShape]).appendTo(shapeTracingMetaContent); } shapeTracingBreadcrumb.text(''); // create collapsible containers shapeTracingMetaContent.find('li:has(ul:has(li))').prepend(glyph); shapeTracingMetaContent.find('ul ul').toggle(false); shapeTracingMetaContent.find('.expando-glyph-container').click(expandCollapseExpando); defaultTab = displayTabShape; }; var defaultTab = displayTabShape; shapeTracingTabsShape.click(function () { displayTabShape(); }); // Model tab var displayTabModel = function () { // toggle the selected class shapeTracingTabs.children('.selected').removeClass('selected'); shapeTracingTabsModel.addClass('selected'); // remove old content shapeTracingMetaContent.empty(); // render the template if (currentShape && shapeTracingMetadataHost[currentShape]) { $("#shape-tracing-tabs-model-template").tmpl(shapeTracingMetadataHost[currentShape].shape.model).appendTo(shapeTracingMetaContent); } shapeTracingBreadcrumb.text(''); // create collapsible containers shapeTracingMetaContent.find('li:has(ul:has(li))').prepend(glyph); shapeTracingMetaContent.find('ul ul').toggle(false); shapeTracingMetaContent.find('.expando-glyph-container').click(expandCollapseExpando); shapeTracingMetaContent.find('.model div.name') .hover( function () { var _this = $(this); $('.shape-tracing-overlay').removeClass('shape-tracing-overlay'); _this.addClass('shape-tracing-overlay'); }, function () { $('.shape-tracing-overlay').removeClass('shape-tracing-overlay'); }) .click(function (event) { // model node is selected var _this = $(this); shapeTracingWindowContent.find('.shape-tracing-selected').removeClass('shape-tracing-selected'); _this.addClass('shape-tracing-selected'); // display breadcrumb var breadcrumb = null; _this.parentsUntil('.model').children('.name').each(function () { if (breadcrumb != null) { breadcrumb = $(this).text() + '.' + breadcrumb; } else { breadcrumb = $(this).text(); } }); // fix enumerable properties display breadcrumb = breadcrumb.replace('.[', '['); shapeTracingBreadcrumb.text('@' + breadcrumb); event.stopPropagation(); }); // open the root node (Model) openExpando(shapeTracingMetaContent.find('.expando-glyph-container:first')) defaultTab = displayTabModel; }; shapeTracingTabsModel.click(function () { displayTabModel(); }); // Placement tab var displayTabPlacement = function () { // toggle the selected class shapeTracingTabs.children('.selected').removeClass('selected'); shapeTracingTabsPlacement.addClass('selected'); // remove old content shapeTracingMetaContent.empty(); // render the template if (currentShape && shapeTracingMetadataHost[currentShape]) { var placementSource = shapeTracingMetadataHost[currentShape].shape.placement; shapeTracingBreadcrumb.text(placementSource); $("#shape-tracing-tabs-placement-template").tmpl(shapeTracingMetadataHost.placement[placementSource]).appendTo(shapeTracingMetaContent); } else { shapeTracingBreadcrumb.text(''); } enableCodeMirror(shapeTracingMetaContent); defaultTab = displayTabPlacement; }; shapeTracingTabsPlacement.click(function () { displayTabPlacement(); }); // Template tab var displayTabTemplate = function () { // toggle the selected class shapeTracingTabs.children('.selected').removeClass('selected'); shapeTracingTabsTemplate.addClass('selected'); // remove old content shapeTracingMetaContent.empty(); // render the template if (currentShape && shapeTracingMetadataHost[currentShape]) { shapeTracingBreadcrumb.text(shapeTracingMetadataHost[currentShape].shape.template); $("#shape-tracing-tabs-template-template").tmpl(shapeTracingMetadataHost[currentShape].shape.templateContent).appendTo(shapeTracingMetaContent); } else { shapeTracingBreadcrumb.text(''); } enableCodeMirror(shapeTracingMetaContent); defaultTab = displayTabTemplate; }; shapeTracingTabsTemplate.click(function () { displayTabTemplate(); }); // HTML tab var displayTabHtml = function () { // toggle the selected class shapeTracingTabs.children('.selected').removeClass('selected'); shapeTracingTabsHtml.addClass('selected'); // remove old content shapeTracingMetaContent.empty(); // render the template if (currentShape && shapeTracingMetadataHost[currentShape]) { $("#shape-tracing-tabs-html-template").tmpl(shapeTracingMetadataHost[currentShape].shape.html).appendTo(shapeTracingMetaContent); } shapeTracingBreadcrumb.text(''); enableCodeMirror(shapeTracingMetaContent); defaultTab = displayTabHtml; }; shapeTracingTabsHtml.click(function () { displayTabHtml(); }); // activates codemirror on specific textareas var enableCodeMirror = function (target) { // if there is a script, and colorization is not enabled yet, turn it on // code mirror seems to work only if the textarea is visible target.find('textarea:visible').each(function () { if ($(this).next('.CodeMirror').length == 0) { CodeMirror.fromTextArea(this, { mode: "razor", tabMode: "indent", readOnly: true, lineNumbers: true }); } }); } // hooks the click event on expandos var expandCollapseExpando = function () { var _this = $(this); if (_this.hasClass("closed") || _this.hasClass("closing")) { openExpando(_this); } else { closeExpando(_this); } return false; }; // automatically expand or collapse shapes in the tree shapeTracingWindowTree.find('.expando-glyph-container').click(expandCollapseExpando); // recursively create a node for the shapes tree function createTreeNode(shapeNode) { var node = $('
  • '); node.attr('tree-shape-id', shapeNode.id); node.get(0).shapeNode = shapeNode; var text = shapeNode.type; // add the hint to the tree node if available if (shapeNode.hint != '') { text += ' [' + shapeNode.hint + ']'; } node.append('
    ' + text + '
    '); var list = $(''); node.append(list); if (shapeNode.children) { for (var shapeId in shapeNode.children) { var child = createTreeNode(shapeNode.children[shapeId]); list.append(child); } } return node; } });