Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

feature #9857 Form Debugger JavaScript improvements (WouterJ)

This PR was submitted for the 2.4 branch but it was merged into the 2.5-dev branch instead (closes #9857).

Discussion
----------

Form Debugger JavaScript improvements

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | -
| Deprecations? | -
| Tests pass?   | -
| Fixed tickets | #9123
| License       | MIT
| Doc PR        | -

Commits
-------

406756d Reverted Sfjs.toggle change
226ac7c Reverted new image
53f164f Fixed asset function
c763d65 Merge pull request #1 from bschussek/issue9857
5afbaeb [WebProfilerBundle] Enlarged the clickable area of the toggle button in the form tree
58559d3 [WebProfilerBundle] Moved toggle icon behind the headlines in the form debugger
a0030b8 [WebProfilerBundle] Changed toggle color back to blue and made headlines in the form debugger clickable
505c5be [WebProfilerBundle] Added "use strict" statements
ebf13ed [WebProfilerBundle] Inverted toggler images and improved button coloring
07994d5 [WebProfilerBundle] Improved JavaScript of the form debugger
11bfda6 [WebProfilerBundle] Vertically centered the icons in the form tree
52b1620 Fixed CS
f21dab2 Added error badge
111a404 Made sections collapsable
60b0764 Improved form tree
0e03189 Expand tree
  • Loading branch information...
commit 9eaed3589261de9a317741121eeb7ea53a3b213a 2 parents 96c4486 + d9bb4ff
@fabpot fabpot authored
View
BIN  src/Symfony/Bundle/FrameworkBundle/Resources/public/images/toggler.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  src/Symfony/Bundle/FrameworkBundle/Resources/public/images/toggler_empty.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  src/Symfony/Bundle/FrameworkBundle/Resources/public/images/toggler_hover.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  src/Symfony/Bundle/FrameworkBundle/Resources/public/images/toggler_hover_empty.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
539 src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
@@ -29,6 +29,16 @@
/*background: #F6F6F6;*/
margin: -30px -40px -40px;
}
+ .toggle-icon {
+ display: inline-block;
+ background: url("{{ asset('bundles/framework/images/toggler.png') }}") no-repeat top left #5eb5e0;
+ }
+ .closed .toggle-icon, .closed.toggle-icon {
+ background-position: bottom left;
+ }
+ .toggle-icon.empty {
+ background-image: url("{{ asset('bundles/framework/images/toggler_empty.png') }}");
+ }
.tree {
width: 230px;
padding: 10px;
@@ -45,45 +55,127 @@
padding: 0;
width: 100%;
}
- .tree a {
- text-decoration: none;
- display: block;
- padding: 5px 7px;
+ .tree .tree-inner {
+ width: 100%;
+ padding: 5px 7px 5px 22px;
border-radius: 6px;
color: #313131;
+ cursor: pointer;
+ position: relative;
+
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
}
- .tree ul ul a {
- padding-left: 22px;
+ .tree a {
+ text-decoration: none;
+ }
+ .tree .toggle-button {
+ /* provide a bigger clickable area than just 10x10px */
+ width: 16px;
+ height: 16px;
+ /* vertically center the button */
+ position: absolute;
+ top: 50%;
+ margin-top: -8px;
+ margin-left: -18px;
+ }
+ .tree .toggle-icon {
+ width: 10px;
+ height: 10px;
+ /* position the icon in the center of the clickable area */
+ margin-left: 3px;
+ margin-top: 3px;
+ background-size: 10px 20px;
+ background-color: #ccc;
+ }
+ .tree .toggle-icon.empty {
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ top: 50%;
+ margin-top: -5px;
+ margin-left: -15px;
+ background-size: 10px 10px;
}
- .tree ul ul ul a {
+ .tree ul ul .tree-inner {
padding-left: 37px;
}
- .tree ul ul ul ul a {
+ .tree ul ul ul .tree-inner {
padding-left: 52px;
}
- .tree ul ul ul ul ul a {
+ .tree ul ul ul ul .tree-inner {
padding-left: 67px;
}
- .tree a:hover {
+ .tree ul ul ul ul ul .tree-inner {
+ padding-left: 82px;
+ }
+ .tree .tree-inner:hover {
background: #dfdfdf;
}
- .tree a.active, a.active:hover {
+ .tree .tree-inner.active, .tree .tree-inner.active:hover {
background: #dfdfdf;
font-weight: bold;
color: #313131;
}
+ .tree .tree-inner.active .toggle-icon, .tree .tree-inner:hover .toggle-icon, .tree .tree-inner.active:hover .toggle-icon {
+ background-image: url("{{ asset('bundles/framework/images/toggler_hover.png') }}");
+ background-color: #aaa;
+ }
+ .tree .tree-inner.active .toggle-icon.empty, .tree .tree-inner:hover .toggle-icon.empty, .tree .tree-inner.active:hover .toggle-icon.empty {
+ background-image: url("{{ asset('bundles/framework/images/toggler_hover_empty.png') }}");
+ }
.tree-details {
border-left: 1px solid #dfdfdf;
background: white;
margin-left: 250px;
padding: 30px 40px 40px;
}
+ .tree-details h3 {
+ position: relative;
+ }
+ .tree-details .toggle-icon {
+ width: 16px;
+ height: 16px;
+ /* vertically center the button */
+ position: absolute;
+ top: 50%;
+ margin-top: -9px;
+ margin-left: 6px;
+ }
.form-type {
color: #999999;
}
.hidden {
display: none;
}
+ .badge-error {
+ float: right;
+ background: #a33;
+ color: #fff;
+ padding: 1px 4px;
+ font-size: 10px;
+ font-weight: bold;
+ vertical-align: middle;
+ border-radius: 6px;
+ }
+ .errors h3 {
+ color: #800;
+ }
+ .errors th, .errors td {
+ border-color: #800;
+ }
+ .errors th {
+ background: #a33;
+ color: #fff;
+ }
+ .errors .toggle-icon {
+ background-color: #a33;
+ }
+ h3 a, h3 a:hover, h3 a:focus {
+ color: inherit;
+ text-decoration: inherit;
+ }
</style>
{% if collector.data.forms|length %}
@@ -93,7 +185,7 @@
<ul>
{% for formName, formData in collector.data.forms %}
- {{ form_tree_entry(formName, formData) }}
+ {{ form_tree_entry(formName, formData, true) }}
{% endfor %}
</ul>
</div>
@@ -107,78 +199,167 @@
{% endif %}
<script>
- function TabView() {
- var _activeLink = null,
- _activeView = null;
+ function Toggler() {
+ "use strict";
+
+ var expand = function (button) {
+ var targetId = button.dataset.toggleTargetId,
+ target = document.getElementById(targetId);
+
+ if (!target) {
+ throw "Toggle target " + targetId + " does not exist";
+ }
+
+ Sfjs.removeClass(button, 'closed');
+ Sfjs.removeClass(target, 'hidden');
+ },
- this.init = function () {
- var links = document.querySelectorAll('.tree a'),
- views = document.querySelectorAll('.tree-details'),
- i,
- l;
+ collapse = function (button) {
+ var targetId = button.dataset.toggleTargetId,
+ target = document.getElementById(targetId);
- for (i = 0, l = links.length; i < l; ++i) {
- (function () {
- var link = links[i];
+ if (!target) {
+ throw "Toggle target " + targetId + " does not exist";
+ }
- link.addEventListener('click', function (e) {
- var href = link.getAttribute('href'),
- targetId = href.substr(href.indexOf('#') + 1),
- view = document.getElementById(targetId);
+ Sfjs.addClass(button, 'closed');
+ Sfjs.addClass(target, 'hidden');
+ },
- if (view) {
- if (null !== _activeLink) {
- Sfjs.removeClass(_activeLink, 'active');
- }
+ toggle = function (button) {
+ if (Sfjs.hasClass(button, 'closed')) {
+ expand(button);
+ } else {
+ collapse(button);
+ }
+ },
- if (null !== _activeView) {
- Sfjs.addClass(_activeView, 'hidden');
- }
+ initButtons = function (buttons) {
+ for (var i = 0, l = buttons.length; i < l; ++i) {
+ var toggleTargetId = buttons[i].dataset.toggleTargetId,
+ toggleTarget = document.getElementById(toggleTargetId);
- Sfjs.addClass(link, 'active');
- Sfjs.removeClass(view, 'hidden');
+ if (!toggleTarget) {
+ throw "Toggle target " + toggleTargetId + " does not exist";
+ }
- _activeLink = link;
- _activeView = view;
- }
+ // correct the initial state of the button
+ if (Sfjs.hasClass(toggleTarget, 'hidden')) {
+ Sfjs.addClass(buttons[i], 'closed');
+ }
+
+ // attach listener for expanding/collapsing the target
+ buttons[i].addEventListener('click', function (e) {
+ toggle(this);
e.preventDefault();
+ e.stopPropagation();
return false;
- })
- }());
- }
+ });
+ }
+ };
+
+ return {
+ initButtons: initButtons,
+
+ toggle: toggle,
+
+ expand: expand,
+
+ collapse: collapse
+ };
+ }
+
+ function TabView() {
+ "use strict";
+
+ var activeTab = null,
+
+ activeTarget = null,
+
+ select = function (tab) {
+ var targetId = tab.dataset.tabTargetId,
+ target = document.getElementById(targetId);
+
+ if (!target) {
+ throw "Tab target " + targetId + " does not exist";
+ }
+
+ if (activeTab) {
+ Sfjs.removeClass(activeTab, 'active');
+ }
- for (i = 0, l = views.length; i < l; ++i) {
- Sfjs.addClass(views[i], 'hidden');
- }
+ if (activeTarget) {
+ Sfjs.addClass(activeTarget, 'hidden');
+ }
+
+ Sfjs.addClass(tab, 'active');
+ Sfjs.removeClass(target, 'hidden');
+
+ activeTab = tab;
+ activeTarget = target;
+ },
+
+ initTabs = function (tabs) {
+ for (var i = 0, l = tabs.length; i < l; ++i) {
+ var targetId = tabs[i].dataset.tabTargetId,
+ target = document.getElementById(targetId);
+
+ if (!target) {
+ throw "Tab target " + targetId + " does not exist";
+ }
+
+ tabs[i].addEventListener('click', function (e) {
+ select(this);
+
+ e.preventDefault();
+ e.stopPropagation();
- if (links.length > 0) {
- Sfjs.addClass(links[0], 'active');
- _activeLink = links[0];
+ return false;
+ });
+
+ Sfjs.addClass(target, 'hidden');
+ }
- if (views.length > 0) {
- Sfjs.removeClass(views[0], 'hidden');
- _activeView = views[0];
+ if (tabs.length > 0) {
+ select(tabs[0]);
}
- }
- }
+ };
+
+ return {
+ initTabs: initTabs,
+
+ select: select
+ };
}
- var tabView = new TabView();
+ var tabTarget = new TabView(),
+ toggler = new Toggler();
- tabView.init();
+ tabTarget.initTabs(document.querySelectorAll('.tree .tree-inner'));
+ toggler.initButtons(document.querySelectorAll('a.toggle-button'));
</script>
{% endblock %}
-{% macro form_tree_entry(name, data) %}
+{% macro form_tree_entry(name, data, expanded) %}
<li>
- <a href="#details_{{ data.id }}">{{ name }}</a>
+ <div class="tree-inner" data-tab-target-id="{{ data.id }}-details">
+ {% if data.children is not empty %}
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-children" href="#"><span class="toggle-icon"></span></a>
+ {% else %}
+ <div class="toggle-icon empty"></div>
+ {% endif %}
+ {{ name }}
+ {% if data.errors is defined and data.errors|length > 0 %}
+ <div class="badge-error">{{ data.errors|length }}</div>
+ {% endif %}
+ </div>
{% if data.children is not empty %}
- <ul>
+ <ul id="{{ data.id }}-children"{% if not expanded %} class="hidden"{% endif %}>
{% for childName, childData in data.children %}
- {{ _self.form_tree_entry(childName, childData) }}
+ {{ _self.form_tree_entry(childName, childData, false) }}
{% endfor %}
</ul>
{% endif %}
@@ -186,7 +367,7 @@
{% endmacro %}
{% macro form_tree_details(name, data) %}
- <div class="tree-details" id="details_{{ data.id }}">
+ <div class="tree-details" id="{{ data.id }}-details">
<h2>
{{ name }}
{% if data.type_class is defined %}
@@ -195,56 +376,76 @@
</h2>
{% if data.errors is defined and data.errors|length > 0 %}
- <h3>Errors</h3>
-
- <table>
- <tr>
- <th width="50%">Message</th>
- <th>Cause</th>
- </tr>
- {% for error in data.errors %}
- <tr>
- <td>{{ error.message }}</td>
- <td><em>Unknown.</em></td>
- </tr>
- {% endfor %}
- </table>
+ <div class="errors">
+ <h3>
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-errors" href="#">
+ Errors
+ <span class="toggle-icon"></span>
+ </a>
+ </h3>
+
+ <table id="{{ data.id }}-errors">
+ <tr>
+ <th width="50%">Message</th>
+ <th>Cause</th>
+ </tr>
+ {% for error in data.errors %}
+ <tr>
+ <td>{{ error.message }}</td>
+ <td><em>Unknown.</em></td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
{% endif %}
{% if data.default_data is defined %}
- <h3>Default Data</h3>
-
- <table>
- <tr>
- <th width="180">Model Format</th>
- <td>
- {% if data.default_data.model is defined %}
- <pre>{{ data.default_data.model }}</pre>
- {% else %}
- <em>same as normalized format</em>
- {% endif %}
- </td>
- </tr>
- <tr>
- <th>Normalized Format</th>
- <td><pre>{{ data.default_data.norm }}</pre></td>
- </tr>
- <tr>
- <th>View Format</th>
- <td>
- {% if data.default_data.view is defined %}
- <pre>{{ data.default_data.view }}</pre>
- {% else %}
- <em>same as normalized format</em>
- {% endif %}
- </td>
- </tr>
- </table>
+ <h3>
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-default_data" href="#">
+ Default Data
+ <span class="toggle-icon"></span>
+ </a>
+ </h3>
+
+ <div id="{{ data.id }}-default_data">
+ <table>
+ <tr>
+ <th width="180">Model Format</th>
+ <td>
+ {% if data.default_data.model is defined %}
+ <pre>{{ data.default_data.model }}</pre>
+ {% else %}
+ <em>same as normalized format</em>
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th>Normalized Format</th>
+ <td><pre>{{ data.default_data.norm }}</pre></td>
+ </tr>
+ <tr>
+ <th>View Format</th>
+ <td>
+ {% if data.default_data.view is defined %}
+ <pre>{{ data.default_data.view }}</pre>
+ {% else %}
+ <em>same as normalized format</em>
+ {% endif %}
+ </td>
+ </tr>
+ </table>
+ </div>
{% endif %}
{% if data.submitted_data is defined %}
- <h3>Submitted Data</h3>
-
+ <h3>
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-submitted_data" href="#">
+ Submitted Data
+ <span class="toggle-icon"></span>
+ </a>
+ </h3>
+
+ <div id="{{ data.id }}-submitted_data">
{% if data.submitted_data.norm is defined %}
<table>
<tr>
@@ -275,69 +476,91 @@
{% else %}
<p><em>This form was not submitted.</em></p>
{% endif %}
+ </div>
{% endif %}
{% if data.passed_options is defined %}
- <h3>Passed Options</h3>
-
- {% if data.passed_options|length %}
- <table>
- <tr>
- <th width="180">Option</th>
- <th>Passed Value</th>
- <th>Resolved Value</th>
- </tr>
- {% for option, value in data.passed_options %}
- <tr>
- <th>{{ option }}</th>
- <td><pre>{{ value }}</pre></td>
- <td>
- {% if data.resolved_options[option] is sameas(value) %}
- <em>same as passed value</em>
- {% else %}
- <pre>{{ data.resolved_options[option] }}</pre>
- {% endif %}
- </td>
- </tr>
- {% endfor %}
- </table>
- {% else %}
- <p><em>No options where passed when constructing this form.</em></p>
- {% endif %}
+ <h3>
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-passed_options" href="#">
+ Passed Options
+ <span class="toggle-icon"></span>
+ </a>
+ </h3>
+
+ <div id="{{ data.id }}-passed_options">
+ {% if data.passed_options|length %}
+ <table>
+ <tr>
+ <th width="180">Option</th>
+ <th>Passed Value</th>
+ <th>Resolved Value</th>
+ </tr>
+ {% for option, value in data.passed_options %}
+ <tr>
+ <th>{{ option }}</th>
+ <td><pre>{{ value }}</pre></td>
+ <td>
+ {% if data.resolved_options[option] is sameas(value) %}
+ <em>same as passed value</em>
+ {% else %}
+ <pre>{{ data.resolved_options[option] }}</pre>
+ {% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p><em>No options where passed when constructing this form.</em></p>
+ {% endif %}
+ </div>
{% endif %}
{% if data.resolved_options is defined %}
- <h3>Resolved Options</h3>
-
- <table>
- <tr>
- <th width="180">Option</th>
- <th>Value</th>
- </tr>
- {% for option, value in data.resolved_options %}
- <tr>
- <th>{{ option }}</th>
- <td><pre>{{ value }}</pre></td>
- </tr>
- {% endfor %}
- </table>
+ <h3>
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-resolved_options" href="#">
+ Resolved Options
+ <span class="toggle-icon"></span>
+ </a>
+ </h3>
+
+ <div id="{{ data.id }}-resolved_options" class="hidden">
+ <table>
+ <tr>
+ <th width="180">Option</th>
+ <th>Value</th>
+ </tr>
+ {% for option, value in data.resolved_options %}
+ <tr>
+ <th>{{ option }}</th>
+ <td><pre>{{ value }}</pre></td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
{% endif %}
{% if data.view_vars is defined %}
- <h3>View Variables</h3>
-
- <table>
- <tr>
- <th width="180">Variable</th>
- <th>Value</th>
- </tr>
- {% for variable, value in data.view_vars %}
- <tr>
- <th>{{ variable }}</th>
- <td><pre>{{ value }}</pre></td>
- </tr>
- {% endfor %}
- </table>
+ <h3>
+ <a class="toggle-button" data-toggle-target-id="{{ data.id }}-view_vars" href="#">
+ View Variables
+ <span class="toggle-icon"></span>
+ </a>
+ </h3>
+
+ <div id="{{ data.id }}-view_vars" class="hidden">
+ <table>
+ <tr>
+ <th width="180">Variable</th>
+ <th>Value</th>
+ </tr>
+ {% for variable, value in data.view_vars %}
+ <tr>
+ <th>{{ variable }}</th>
+ <td><pre>{{ value }}</pre></td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
{% endif %}
</div>
View
10 src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
@@ -22,15 +22,19 @@
},
hasClass = function(el, klass) {
- return el.className.match(new RegExp('\\b' + klass + '\\b'));
+ return el.className && el.className.match(new RegExp('\\b' + klass + '\\b'));
},
removeClass = function(el, klass) {
- el.className = el.className.replace(new RegExp('\\b' + klass + '\\b'), ' ');
+ if (el.className) {
+ el.className = el.className.replace(new RegExp('\\b' + klass + '\\b'), ' ');
+ }
},
addClass = function(el, klass) {
- if (!hasClass(el, klass)) { el.className += " " + klass; }
+ if (!hasClass(el, klass)) {
+ el.className += " " + klass;
+ }
},
getPreference = function(name) {
Please sign in to comment.
Something went wrong with that request. Please try again.