diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 750a692..f641119 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,7 +8,7 @@ * A sentence describing each fix ### Update -* A sentence describing each udpate +* A sentence describing each update ### New * A sentence describing each new feature diff --git a/README.md b/README.md index b355e08..1db4edf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Inspector -An extension to inspect details of elements. +**Inspector** is an extension to inspect the details of Adapt elements. ## Installation @@ -67,3 +67,14 @@ An extension to inspect details of elements. [ "menu", "menuItem", "page", "article", "block", "component" ] + +## Limitations + +No known limitations. + +---------------------------- + +**Author / maintainer:** CGKineo
+**Accessibility support:** WAI AA
+**RTL support:** Yes
+**Cross-platform coverage:** Chrome, Chrome for Android, Firefox (ESR + latest version), Edge, Safari for macOS/iOS/iPadOS, Opera
diff --git a/js/InspectorContainerView.js b/js/InspectorContainerView.js new file mode 100644 index 0000000..854e66a --- /dev/null +++ b/js/InspectorContainerView.js @@ -0,0 +1,59 @@ +import Adapt from 'core/js/adapt'; + +class InspectorContainerView extends Backbone.View { + initialize() { + const id = this.model.get('_id'); + + this.listenTo(Adapt, 'remove', this.remove); + this.addTracURL(); + this.$el.data(this.model); + Adapt.trigger('inspector:id', id); + } + + events() { + return { + mouseenter: 'onHover', + mouseleave: 'onHover', + touchend: 'onTouch' + }; + } + + addTracURL() { + const config = Adapt.config.get('_inspector')._trac; + if (!config || !config._isEnabled) return; + + const params = config._params || { + summary: '{{_id}}{{#if displayTitle}} {{{displayTitle}}}{{/if}}{{inspector_location}}' + }; + + const $div = $('
'); + const data = this.model.toJSON(); + let tracUrl = `${config._url}/newticket?`; + + for (const key in params) { + if (!Object.prototype.hasOwnProperty.call(params, key)) continue; + + const value = $div.html(Handlebars.compile(params[key])(data)).text(); + + tracUrl += `&${key}=${encodeURIComponent(value)}`; + } + + this.model.set('_tracUrl', tracUrl); + } + + onHover() { + _.defer(() => Adapt.trigger('inspector:hover')); + } + + onTouch(event) { + if (event.originalEvent.stopInspectorPropagation) return; + + event.originalEvent.stopInspectorPropagation = true; + + if ($(event.target).is('[class*=inspector-]')) return; + + Adapt.trigger('inspector:touch', this.$el); + } +} + +export default InspectorContainerView; diff --git a/js/InspectorView.js b/js/InspectorView.js new file mode 100644 index 0000000..2dce72c --- /dev/null +++ b/js/InspectorView.js @@ -0,0 +1,115 @@ +import Adapt from 'core/js/adapt'; +import device from 'core/js/device'; + +class InspectorView extends Backbone.View { + className() { + return 'inspector'; + } + + events() { + return { + mouseleave: 'onLeave' + }; + } + + initialize() { + const config = Adapt.config.get('_inspector'); + if (device.touch && config._isDisabledOnTouch) return; + + _.bindAll(this, 'onLeave', 'pushId', 'setVisibility', 'updateInspector', 'onResize', 'remove'); + + this.listenTo(Adapt, { + 'inspector:id': this.pushId, + 'inspector:hover': this.setVisibility, + 'inspector:touch': this.updateInspector, + 'device:resize': this.onResize, + remove: this.remove + }).render(); + + this.ids = []; + } + + render() { + $('#wrapper').append(this.$el); + }; + + pushId(id) { + this.ids.push(id); + } + + setVisibility() { + if (this.$el.is(':hover')) return; + + const reversedIds = this.ids.toReversed(); + for (const id of reversedIds) { + const $hovered = $(`[data-adapt-id="${id}"]:hover`); + + if ($hovered.length) return this.updateInspector($hovered); + } + + $('.inspector-visible').removeClass('inspector-visible'); + this.$el.hide(); + } + + updateInspector($hovered) { + const $previous = $('.inspector-visible'); + if ($hovered.is($previous.last())) return; + + const data = []; + const template = Handlebars.templates.inspector; + + $previous.removeClass('inspector-visible'); + + this.addOverlappedElements($hovered).each(function() { + const $element = $(this); + const attributes = $element.data().attributes; + if (!attributes) return; + + data.push(attributes); + $element.addClass('inspector-visible'); + }); + + this.$el.html(template(data)).removeAttr('style').removeClass('inline'); + + const offset = $hovered.offset(); + const offsetTop = offset.top; + const targetTop = offsetTop - this.$el.outerHeight(); + const shouldBeInline = targetTop < 0; + + this.$el.css({ + top: shouldBeInline ? offsetTop : targetTop, + left: offset.left + $hovered.outerWidth() / 2 - this.$el.width() / 2 + }).toggleClass('inline', shouldBeInline); + } + + addOverlappedElements($hovered) { + this.ids.forEach((id) => { + const $element = $(`[data-adapt-id="${id}"]`); + this.checkOverlap($element, $hovered); + }); + + return $hovered; + } + + checkOverlap($element, $hovered) { + const areOffsetsEqual = _.isEqual($element.offset(), $hovered.offset()); + const areWidthsEqual = $element.width() === $hovered.width(); + const isOverlapped = $element.height() && areOffsetsEqual && areWidthsEqual; + + if (isOverlapped) $hovered = $hovered.add($element); + } + + onResize() { + const $hovered = $('.inspector-visible'); + if (!$hovered.length) return; + + $hovered.removeClass('inspector-visible'); + this.updateInspector($hovered.last()); + } + + onLeave() { + _.defer(this.setVisibility.bind(this)); + } +}; + +export default InspectorView; diff --git a/js/adapt-inspector.js b/js/adapt-inspector.js index d06d10e..9831034 100644 --- a/js/adapt-inspector.js +++ b/js/adapt-inspector.js @@ -1,198 +1,38 @@ -define([ - 'core/js/adapt', - 'core/js/device' -], function(Adapt, device) { - - const InspectorView = Backbone.View.extend({ - - className: 'inspector', - - ids: [], - - initialize: function() { - const config = Adapt.config.get('_inspector'); - if (device.touch && config._isDisabledOnTouch) return; - - this.listenTo(Adapt, { - 'inspector:id': this.pushId, - 'inspector:hover': this.setVisibility, - 'inspector:touch': this.updateInspector, - 'device:resize': this.onResize, - remove: this.remove - }).render(); - }, - - events: { - mouseleave: 'onLeave' - }, - - render: function() { - $('#wrapper').append(this.$el); - }, - - pushId: function(id) { - this.ids.push(id); - }, - - setVisibility: function() { - if (this.$el.is(':hover')) return; - - for (let i = this.ids.length - 1; i >= 0; --i) { - const $hovered = $("[data-adapt-id='" + this.ids[i] + "']:hover"); - - if ($hovered.length) return this.updateInspector($hovered); - } - - $('.inspector-visible').removeClass('inspector-visible'); - this.$el.hide(); - }, - - updateInspector: function($hovered) { - const $previous = $('.inspector-visible'); - - if ($hovered.is($previous.last())) return; - - const data = []; - const template = Handlebars.templates.inspector; - - $previous.removeClass('inspector-visible'); - - this.addOverlappedElements($hovered).each(function() { - const $element = $(this); - const attributes = $element.data().attributes; - - if (!attributes) return; - - data.push(attributes); - $element.addClass('inspector-visible'); - }); - - this.$el.html(template(data)).removeAttr('style').removeClass('inline'); - - const offset = $hovered.offset(); - const offsetTop = offset.top; - const targetTop = offsetTop - this.$el.outerHeight(); - const shouldBeInline = targetTop < 0; - - this.$el.css({ - top: shouldBeInline ? offsetTop : targetTop, - left: offset.left + $hovered.outerWidth() / 2 - this.$el.width() / 2 - }).toggleClass('inline', shouldBeInline); - }, - - addOverlappedElements: function($hovered) { - const checkOverlap = function() { - const $element = $(this); - - const isOverlapped = $element.height() && _.isEqual($element.offset(), $hovered.offset()) && $element.width() === $hovered.width(); - - if (isOverlapped) $hovered = $hovered.add($element); - }; - - for (let i = this.ids.length - 1; i >= 0; --i) { - $("[data-adapt-id='" + this.ids[i] + "']").each(checkOverlap); - } - - return $hovered; - }, - - onResize: function() { - const $hovered = $('.inspector-visible'); - - if (!$hovered.length) return; - - $hovered.removeClass('inspector-visible'); - this.updateInspector($hovered.last()); - }, - - onLeave: function() { - _.defer(this.setVisibility.bind(this)); - } - - }); - - const InspectorContainerView = Backbone.View.extend({ - - initialize: function() { - const id = this.model.get('_id'); - - this.listenTo(Adapt, 'remove', this.remove).addTracUrl(id); - this.$el.data(this.model); - Adapt.trigger('inspector:id', id); - }, - - events: { - mouseenter: 'onHover', - mouseleave: 'onHover', - touchend: 'onTouch' - }, - - addTracUrl: function(id) { - const config = Adapt.config.get('_inspector')._trac; - - if (!config || !config._isEnabled) return; - - const params = config._params || { - summary: '{{_id}}{{#if displayTitle}} {{{displayTitle}}}{{/if}}{{inspector_location}}' - }; - - const $div = $('
'); - const data = this.model.toJSON(); - let tracUrl = config._url + '/newticket?'; - - for (const key in params) { - if (!Object.prototype.hasOwnProperty.call(params, key)) continue; - - const value = $div.html(Handlebars.compile(params[key])(data)).text(); - - tracUrl += '&' + key + '=' + encodeURIComponent(value); - } - - this.model.set('_tracUrl', tracUrl); - }, - - onHover: function() { - _.defer(function() { Adapt.trigger('inspector:hover'); }); - }, - - onTouch: function(event) { - if (event.originalEvent.stopInspectorPropagation) return; - - event.originalEvent.stopInspectorPropagation = true; - - if (!$(event.target).is('[class*=inspector-]')) { - Adapt.trigger('inspector:touch', this.$el); - } - } - - }); +import Adapt from 'core/js/adapt'; +import location from 'core/js/location'; +import InspectorContainerView from './InspectorContainerView'; +import InspectorView from './InspectorView'; + +class Inspector extends Backbone.Controller { + initialize() { + this.listenToOnce(Adapt, 'app:dataReady', this.initInspector); + } - function getLocationString(context) { + getLocationString(context) { const data = context.data.root; const id = data.length ? data[0]._id : data._id; - const location = Adapt.location; const locationId = location._currentId; const locationType = location._contentType; return id !== locationId ? ' (' + locationType + ' ' + locationId + ')' : ''; } - Adapt.once('app:dataReady', function() { + initInspector() { const config = Adapt.config.get('_inspector'); - if (!config || !config._isEnabled) return; - const views = config._elementsToInspect || [ 'menu', 'menuItem', 'page', 'article', 'block', 'component' ]; - - const eventList = views.map(function(view) { return view + 'View:postRender'; }); + const defaultElements = [ 'menu', 'menuItem', 'page', 'article', 'block', 'component' ]; + const views = config._elementsToInspect || defaultElements; + const eventList = views.map(view => { return view + 'View:postRender'; }); - Handlebars.registerHelper('inspector_location', getLocationString); + Handlebars.registerHelper('inspector_location', this.getLocationString); - Adapt.on('router:location', function() { + Adapt.on('router:location', () => { new InspectorView(); - }).on(eventList.join(' '), function(view) { + }).on(eventList.join(' '), (view) => { new InspectorContainerView({ el: view.$el, model: view.model }); }); - }); + } +} -}); +export default new Inspector();