diff --git a/LICENSE b/LICENSE
index 947ddef..11b7684 100755
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
BSD 3-Clause License
-Copyright (c) 2017, FLX Labs
+Copyright (c) 2017 - 2018, FLX Labs
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/composer.json b/composer.json
index 72bfafe..234bea9 100755
--- a/composer.json
+++ b/composer.json
@@ -1,27 +1,28 @@
{
- "name": "flxlabs/silverstripe-pagesections",
- "version": "0.1.2",
- "description": "Adds configurable page sections and elements to your SilverStripe project.",
- "type": "silverstripe-module",
- "homepage": "http://github.com/flxlabs/silverstripe-pagesections",
- "keywords": ["silverstripe", "sections", "elements", "page sections", "page elements"],
- "license": "MIT",
- "authors": [{
- "name": "Marco Crespi",
- "email": "mrc@flxlabs.com"
- }],
- "support": {
- "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues"
- },
- "require": {
- "silverstripe/framework": "^4.0.1",
- "symbiote/silverstripe-gridfieldextensions": "^3"
- },
- "extra": {
- "installer-name": "pagesections",
- "expose": [
- "css",
- "javascript"
- ]
- }
+ "name": "flxlabs/silverstripe-pagesections",
+ "version": "0.1.2",
+ "description": "Adds configurable page sections and elements to your SilverStripe project.",
+ "type": "silverstripe-module",
+ "homepage": "http://github.com/flxlabs/silverstripe-pagesections",
+ "keywords": ["silverstripe", "sections", "elements", "page sections", "page elements"],
+ "license": "MIT",
+ "authors": [{
+ "name": "Marco Crespi",
+ "email": "mrc@flxlabs.com"
+ }],
+ "support": {
+ "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues"
+ },
+ "require": {
+ "silverstripe/framework": "^4.0.1",
+ "symbiote/silverstripe-gridfieldextensions": "^3",
+ "unclecheese/betterbuttons": "dev-feature/ss4-upgrade"
+ },
+ "extra": {
+ "installer-name": "pagesections",
+ "expose": [
+ "css",
+ "javascript"
+ ]
+ }
}
diff --git a/css/GridFieldPageSectionsExtension.css b/css/GridFieldPageSectionsExtension.css
index 6ba66ea..acfd1d1 100755
--- a/css/GridFieldPageSectionsExtension.css
+++ b/css/GridFieldPageSectionsExtension.css
@@ -22,6 +22,7 @@
list-style-type: none;
transition: all .3s ease;
}
+
.treenav-menu li:hover {
background-color: #FEFAD5;
}
@@ -32,6 +33,7 @@
border-bottom: 1px solid #CCC;
padding-left: 8px;
}
+
.treenav-menu li.header:hover {
background: none;
}
@@ -40,96 +42,161 @@
border-top: 1px solid #CCC;
}
+/* hierarchical gridfield */
-.ss-gridfield-pagesections .col-treenav {
+.cms table.ss-gridfield-table tr {
height: 100%;
- padding-left: 0;
- /*padding: 0 !important;*/
}
-.ss-gridfield-pagesections .col-treenav > button {
- /*width: 100% !important;*/
- height: 100% !important;
- font-weight: bold !important;
- /*font-size: 150% !important;*/
- color: black !important;
- border: none;
- background: transparent;
- padding-right: 0;
+.cms table.ss-gridfield-table tbody td.col-treenav {
+ padding: 0;
+ height: 100%;
+ vertical-align: middle;
}
-.ss-gridfield-pagesections .col-treenav > button, .ss-gridfield-pagesections .col-treenav > button > span {
- padding: 0 !important;
- margin: 0 !important;
- text-align: left !important;
+.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__inner {
+ display: flex;
+ height: 100%;
+ flex-flow: row nowrap;
+ justify-content: flex-start;
+ align-items: center;
}
-.ss-gridfield-pagesections .col-treenav > button:focus {
- background: none !important;
- box-shadow: none!important;
- border: none !important;
- background-color: none !important;
+.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__text {
+ padding: 8px 8px 8px 0;
}
-.ss-gridfield-pagesections .col-treenav > button > span > span.is-closed{
+.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__classname {
+ font-size: 87%;
+}
- font-size: 100%;
+.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__title {
+ font-weight: bold;
}
-.ss-gridfield-pagesections .col-treenav > button > span > span.is-open{
- font-size: 96%;
- margin-left: -0.1em;
+.cms table.ss-gridfield-table tbody td.col-treenav button {
+ width: 2em;
+ height: 100%;
+ display: block;
+ float: left;
+ margin: 0 0.5em 0 0;
+ padding: 0;
+ border-radius: 0;
+ position: relative;
}
-.ss-gridfield-pagesections .col-treenav > button > span > span.is-end{
- font-size: 110%;
- margin-left: -0.1em;
+.cms table.ss-gridfield-table tbody td.col-treenav button.ui-state-disabled {
+ opacity: 1;
+ filter: Alpha(Opacity=100);
+ background-image: none;
+ background: none;
+ box-shadow: none;
}
-.ss-gridfield-pagesections .col-treenav > button > span > span {
- margin-right: 1em;
- text-transform: capitalize;
- vertical-align: middle;
+.cms table.ss-gridfield-table tbody td.col-treenav button.ui-state-disabled:active {
+ border: none;
+ background: none;
+ box-shadow: none;
}
-.ss-gridfield-pagesections .col-treenav > button > span {
- margin-right: 30px !important;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow-x: hidden;
+.cms table.ss-gridfield-table tbody td.col-treenav button svg {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
}
-.ss-gridfield-pagesections .col-treenav > button.level0 > span {
- margin-left: 1em !important;
+.cms table.ss-gridfield-table tbody td.col-treenav button.level1 {
+ margin-left: 2em;
}
-.ss-gridfield-pagesections .col-treenav > button.level1 > span {
- margin-left: 2em !important;
+
+.cms table.ss-gridfield-table tbody td.col-treenav button.level2 {
+ margin-left: 4em;
}
-.ss-gridfield-pagesections .col-treenav > button.level2 > span {
- margin-left: 4em !important;
+
+.cms table.ss-gridfield-table tbody td.col-treenav button.level3 {
+ margin-left: 6em;
+}
+
+.cms table.ss-gridfield-table tbody td.col-treenav button.level4 {
+ margin-left: 8em;
}
-.ss-gridfield-pagesections .col-treenav > button.level3 > span {
- margin-left: 6em !important;
+
+.cms table.ss-gridfield-table tbody td.col-treenav button.level5 {
+ margin-left: 10em;
}
-.ss-gridfield-pagesections .col-treenav > button.level4 > span {
- margin-left: 8em !important;
+.cms table.ss-gridfield-table tbody td.col-treenav button.level6 {
+ margin-left: 12em;
}
+/**
+ * action col
+ */
+
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tr td.col-actions {
+ width: 68px;
+}
+
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td.col-actions a.view-link,
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td.col-actions a.edit-link {
+ margin-top: -5px;
+}
+
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td button.col-actions__button {
+ margin: 2px;
+}
+
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td button.col-actions__button .ui-button-text {
+ line-height: 0;
+ padding: 0;
+}
+
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td button.col-actions__button .col-actions__button__icon svg {
+ display: block;
+}
/**
* Orderable rows
*/
+.col-treenav__draggable {
+ z-index: 300;
+ background: #f6f7f8;
+ padding: 2px 4px;
+ border-radius: 2px;
+ box-shadow: 0 0 1px #ccc;
+}
+
+.col-treenav__draggable.state-active {
+ border: 1px solid #417505;
+}
+
+.col-treenav__draggable:before {
+ position: absolute;
+ content: "";
+ left: -19px;
+ top: -19px;
+ width: 8px;
+ height: 8px;
+ border: 3px solid #4a4a4a;
+ border-radius: 50%;
+ box-sizing: border-box;
+}
+
.ss-gridfield-pagesections thead tr th.col-Reorder span {
padding: 0 !important;
margin-left: 8px;
}
+.cms .ss-gridfield-pagesections table.ss-gridfield-table tr td.col-reorder {
+ position: relative;
+ padding: 0;
+ width: 16px;
+}
+
.ss-gridfield-pagesections .col-reorder {
position: relative;
- padding: 0 !important;
- width: 16px !important;
}
.ss-gridfield-pagesections .col-reorder .handle {
@@ -154,26 +221,60 @@
.ss-gridfield-pagesections .col-reorder .ui-droppable {
position: absolute;
- left: 0;
- width: 100%;
+ left: 100%;
+ width: 100vw;
z-index: 100;
display: none;
}
+.ss-gridfield-pagesections .col-reorder .ui-droppable svg {
+ position: absolute;
+ left: 0;
+ top: 50%;
+ z-index: 100;
+ transform: translate(0, -50%);
+}
+
+.ss-gridfield-pagesections .col-reorder .ui-droppable svg path {
+ fill: #A8CB7F;
+}
+
.ss-gridfield-pagesections .col-reorder .ui-droppable.before {
top: 0;
- height: 25%;
- background-color: rgba(0, 100, 0, 0.4);
+ height: 50%;
+}
+
+.ss-gridfield-pagesections .col-reorder .ui-droppable.before svg {
+ top: 0;
}
.ss-gridfield-pagesections .col-reorder .ui-droppable.middle {
- top: 25%;
- height: 50%;
- background-color: rgba(0, 0, 100, 0.4);
+ left: auto;
+ right: 0;
+ top: 0;
+ height: 100%;
+}
+
+.ss-gridfield-pagesections .col-reorder .ui-droppable.middle svg {
+ left: auto;
+ right: 0;
+ transform: scale(-1) translate(0, 50%);
}
.ss-gridfield-pagesections .col-reorder .ui-droppable.after {
- bottom: 0;
- height: 25%;
- background-color: rgba(0, 100, 0, 0.4);
+ bottom: -1px;
+ height: 50%;
+}
+
+.ss-gridfield-pagesections .col-reorder .ui-droppable.after svg {
+ top: 100%;
+ margin-top: -1px;
+}
+
+.ss-gridfield-pagesections .col-reorder .ui-droppable.state-active {
+ z-index: 200;
+}
+
+.ss-gridfield-pagesections .col-reorder .ui-droppable.state-active svg path {
+ fill: #417505;
}
diff --git a/examples/ImageElement.php_ b/examples/ImageElement.php_
index d355b48..1e0cf13 100755
--- a/examples/ImageElement.php_
+++ b/examples/ImageElement.php_
@@ -1,10 +1,15 @@
'Image',
+ 'Image' => Image::class,
);
public function getCMSFields(){
diff --git a/examples/TextElement.php_ b/examples/TextElement.php_
index 33cf7d6..3ceaecf 100755
--- a/examples/TextElement.php_
+++ b/examples/TextElement.php_
@@ -1,7 +1,10 @@
'HTMLText',
diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js
index dbf35aa..fdd9234 100755
--- a/javascript/GridFieldPageSectionsExtension.js
+++ b/javascript/GridFieldPageSectionsExtension.js
@@ -1,10 +1,10 @@
-(function($) {
- $.entwine("ss", function($) {
+(function ($) {
+ $.entwine("ss", function ($) {
// Recursively hide a data-grid row and it's children
- var hideRow = function($row) {
+ var hideRow = function ($row) {
var id = $row.data("id");
- $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each(function() {
+ $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each(function () {
hideRow($(this).parent());
});
$row.hide();
@@ -18,128 +18,134 @@
}
});
- // Context menu click
- $(document).on("click", ".treenav-menu li", function(event) {
- var $this = $(this);
- var $menu = $this.parents(".treenav-menu");
- var $gridfield = $(".ss-gridfield-pagesections[data-id='" + $menu.data("grid-id") + "']").find("tbody");
- var newType = $this.data("type");
-
- // If we don't have a type then the user clicked a header or some random thing
- if (!newType) return;
-
- if (newType === "__REMOVE__") {
- $gridfield.removeElement($menu.data("row-id"), $menu.data("parent-id"));
- } else if (newType === "__DELETE__") {
- if (!confirm("Are you sure you want to remove this element? All children will be orphans!"))
- return;
-
- $gridfield.deleteElement($menu.data("row-id"));
- } else {
- $gridfield.addElement($menu.data("row-id"), newType);
- }
-
- $this.parents(".treenav-menu").hide();
- });
-
// Show context menu
$(".ss-gridfield-pagesections tbody").entwine({
- oncontextmenu: function(event) {
- $target = $(event.target);
-
- var grid = this.getGridField();
- var id = grid.data("id");
- var rowId = $target.parents(".ss-gridfield-item").data("id");
- var $treeNav = $target.hasClass("col-treenav") ? $target :
- $target.parents(".col-treenav").first();
-
- // If we don't have a col-treenav the user clicked on another column
- if ($treeNav.length <= 0) return;
- event.preventDefault();
-
- var parentId = null;
- var parentName = null;
- var level = $treeNav.data("level");
- if (level > 0) {
- // Go up through the rows and find the first row with lower level (=parent)
- $parent = $treeNav.parents(".ss-gridfield-item").prev();
- while ($parent.length > 0 &&
- $parent.find(".col-treenav").data("level") >= level) {
- $parent = $parent.prev();
- }
- if ($parent != null) {
- parentId = $parent.data("id");
- parentName = $parent.find(".col-treenav > span").html();
- }
- }
-
- var elems = $treeNav.data("allowed-elements");
- $menu = $("
");
- $menu.css({
- top: event.pageY + "px",
- left: event.pageX + "px"
- });
- $(document.body).append($menu);
-
- $menu.data({
- gridId: id,
- rowId: rowId,
- parentId: parentId,
- });
- $menu.append("");
- $.each(elems, function(key, value) {
- $menu.append("" + value + "");
- });
- $menu.append("");
- $menu.append("Remove from " +
- (parentId ? parentName : "page") + "");
- $menu.append("Delete");
- $menu.show();
- },
- addElement: function(id, elemType) {
+ addElement: function (id, elemType) {
var grid = this.getGridField();
grid.reload({
url: grid.data("url-add"),
- data: [
- { name: "id", value: id },
- { name: "type", value: elemType },
+ data: [{
+ name: "id",
+ value: id
+ },
+ {
+ name: "type",
+ value: elemType
+ },
]
});
},
- removeElement: function(id, parentId) {
+ removeElement: function (id, parentId) {
var grid = this.getGridField();
grid.reload({
url: grid.data("url-remove"),
- data: [
- { name: "id", value: id },
- { name: "parentId", value: parentId },
+ data: [{
+ name: "id",
+ value: id
+ },
+ {
+ name: "parentId",
+ value: parentId
+ },
]
});
},
- deleteElement: function(id) {
+ deleteElement: function (id) {
var grid = this.getGridField();
grid.reload({
url: grid.data("url-delete"),
- data: [
- { name: "id", value: id },
- ]
+ data: [{
+ name: "id",
+ value: id
+ }, ]
});
},
- onadd: function() {
+ onadd: function () {
var grid = this.getGridField();
+ var thisGrid = this;
- $("tr.ss-gridfield-item").each(function() {
+ $("tr.ss-gridfield-item").each(function () {
var $this = $(this);
+ // actions
+ $this.find(".col-actions .add-button").click(function (event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ $target = $(event.target);
+ var elems = $target.data("allowed-elements");
+
+ var id = grid.data("id");
+ var rowId = $target.parents(".ss-gridfield-item").data("id");
+
+
+ var $menu = $("");
+ $menu.css({
+ top: event.pageY + "px",
+ left: event.pageX + "px"
+ });
+ $(document.body).append($menu);
+
+ $menu.append("");
+ $.each(elems, function (key, value) {
+ var $li = $("" + value + "")
+ $li.click(function () {
+ thisGrid.addElement(rowId, key);
+ $menu.remove();
+ })
+ $menu.append($li);
+ });
+ $menu.show();
+ });
+
+ $this.find(".col-actions .delete-button").click(function (event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+
+ $target = $(event.target);
+
+ var id = grid.data("id");
+ var rowId = $target.parents(".ss-gridfield-item").data("id");
+ var parentId = $target.data("parent-id");
+
+ var $menu = $("");
+ $menu.css({
+ top: event.pageY + "px",
+ left: event.pageX + "px"
+ });
+ $(document.body).append($menu);
+
+ $menu.append("");
+
+ var $li = $("" + ss.i18n._t('PageSections.GridField.RemoveAChild', 'Remove') + "")
+ $li.click(function () {
+ thisGrid.removeElement(rowId, parentId);
+ $menu.remove();
+ })
+ $menu.append($li);
+ if ($target.data("used-count") < 2) {
+ var $li = $("" + ss.i18n._t('PageSections.GridField.DeleteAChild', 'Finally delete') + "")
+ $li.click(function () {
+ thisGrid.deleteElement(rowId, $menu.data("parent-id"));
+ $menu.remove();
+ })
+ $menu.append($li);
+ }
+
+ $menu.show();
+ });
+
+ // reorder
+ var icon = ""
$col = $this.find(".col-reorder");
- $col.append("");
- $col.find("div").each(function() {
+ $col.append("" + icon + "
" + icon + "
" + icon + "
");
+ $col.find("div").each(function () {
$(this).droppable({
+ hoverClass: "state-active",
tolerance: "pointer",
- drop: function(event, ui) {
+ drop: function (event, ui) {
$drop = $(this);
var type = "before";
@@ -187,36 +193,85 @@
value: sort,
}],
});
+ // we alter the state of the published / saved buttons
+ $('.cms-edit-form .Actions #Form_EditForm_action_publish').button({
+ showingAlternate: true
+ });
+ $('.cms-preview').entwine('.ss.preview').changeState('StageLink');
+
},
});
});
-
-
$this.draggable({
revert: "invalid",
- helper: function() {
- var clone = $this.clone().css("z-index", 200).find(".ui-droppable").remove().end();
- // Timeout is needed otherwise the draggable position is messed up
- setTimeout(function() {
- hideRow($this);
- }, 1);
- return clone;
+ cursor: "crosshair",
+ cursorAt: {
+ top: -15,
+ left: -15
+ },
+ activeClass: "state-active",
+ hoverClass: "state-active",
+ tolerance: "pointer",
+ greedy: true,
+ helper: function () {
+ var $tr = $this.parents("tr.ss-gridfield-item");
+ var $helper = $(
+ "" +
+ $this.find(".col-treenav__title").text() +
+ "
"
+ )
+ $this.css("opacity", 0.6)
+
+ return $helper;
},
- start: function() {
+ start: function () {
var element = $this.data("class");
- $(".ui-droppable").each(function() {
+ $(".ui-droppable").each(function () {
var $drop = $(this);
var $treenav = $drop.parent().siblings(".col-treenav");
+ var isOpen = $treenav.find("button").hasClass("is-open");
+ var $tr = $drop.parents("tr.ss-gridfield-item");
// Check if we're allowed to drop the element on the specified drop point.
+ // dont enable dropping on itself
+ if ($tr.data("id") == $this.data("id")) return
+
+ // dont enable dropping on .before of itself
+ if ($drop.hasClass("before") && $tr.prev().data("id") == $this.data("id")) return
// Depending on where we drop it (before, middle or after) we have to either
+ // don't show middle if open
+ if (
+ $drop.hasClass("middle") &&
+ isOpen
+ ) {
+ return;
+ }
+ // let's handle level 0 if not open
+ else if (
+ $treenav.data("level") == 0 &&
+ (
+ $drop.hasClass("before") ||
+ (
+ $drop.hasClass("after") &&
+ !isOpen
+ )
+ )
+ ) {
+ var allowed = $treenav.data("allowed-parent-elements");
+ if (!allowed[element]) return;
+ }
// check our allowed children, or the allowed children of our parent row.
- if ($drop.hasClass("before") ||
- ($drop.hasClass("after") && !$treenav.find("button").hasClass("is-open"))) {
-
- var $parent = $treenav.parent().siblings("[data-id=" +
- $treenav.data("parent") + "]").first();
+ else if (
+ $drop.hasClass("before") ||
+ (
+ $drop.hasClass("after") &&
+ !isOpen
+ )
+ ) {
+ var $parent = $treenav.parent().siblings(
+ "[data-id=" + $treenav.data("parent") + "]"
+ ).first();
var allowed = $parent.find(".col-treenav").data("allowed-elements");
if (allowed && !allowed[element]) return;
@@ -228,17 +283,17 @@
$(this).show();
});
},
- stop: function(event, ui) {
+ stop: function (event, ui) {
$(".ui-droppable").hide();
// Show the previous elements. If the user made an invalid movement then
// we want this to show anyways. If he did something valid the grid will
// refresh so we don't care if it's visible behind the loading icon.
- $("tr.ss-gridfield-item").show();
+ $("tr.ss-gridfield-item").css("opacity", "")
},
});
});
},
- onremove: function() {
+ onremove: function () {
if (this.data('sortable')) {
this.sortable("destroy");
}
diff --git a/javascript/lang/de.js b/javascript/lang/de.js
new file mode 100644
index 0000000..89b4d9a
--- /dev/null
+++ b/javascript/lang/de.js
@@ -0,0 +1,12 @@
+if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') {
+ if (typeof(console) !== 'undefined') { // eslint-disable-line no-console
+ console.error('Class ss.i18n not defined'); // eslint-disable-line no-console
+ }
+} else {
+ ss.i18n.addDictionary('en', {
+ "PageSections.GridField.AddAChild": "Unterelement hinzufügen",
+ "PageSections.GridField.Delete": "Löschen",
+ "PageSections.GridField.DeleteAChild": "Endgültig löschen",
+ "PageSections.GridField.RemoveAChild": "Entfernen",
+ });
+}
diff --git a/javascript/lang/en.js b/javascript/lang/en.js
new file mode 100644
index 0000000..615f3ee
--- /dev/null
+++ b/javascript/lang/en.js
@@ -0,0 +1,12 @@
+if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') {
+ if (typeof(console) !== 'undefined') { // eslint-disable-line no-console
+ console.error('Class ss.i18n not defined'); // eslint-disable-line no-console
+ }
+} else {
+ ss.i18n.addDictionary('en', {
+ "PageSections.GridField.AddAChild": "Add a child",
+ "PageSections.GridField.Delete": "Delete",
+ "PageSections.GridField.DeleteAChild": "Finally delete",
+ "PageSections.GridField.RemoveAChild": "Remove",
+ });
+}
diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php
index 0a849c2..65739d6 100755
--- a/src/GridFieldPageSectionsExtension.php
+++ b/src/GridFieldPageSectionsExtension.php
@@ -13,6 +13,7 @@
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\ArrayList;
+use SilverStripe\View\ArrayData;
use SilverStripe\ORM\DataObject;
use SilverStripe\View\Requirements;
use SilverStripe\View\ViewableData;
@@ -32,8 +33,7 @@ class GridFieldPageSectionsExtension implements
"handleAdd",
"handleRemove",
"handleDelete",
- "handleReorder",
- "handleMoveToPage"
+ "handleReorder"
];
@@ -54,8 +54,7 @@ public function getURLHandlers($grid) {
"POST add" => "handleAdd",
"POST remove" => "handleRemove",
"POST delete" => "handleDelete",
- "POST reorder" => "handleReorder",
- "POST movetopage" => "handleMoveToPage"
+ "POST reorder" => "handleReorder"
];
}
@@ -67,6 +66,7 @@ public function getHTMLFragments($field) {
$moduleDir = self::getModuleDir();
Requirements::css($moduleDir . "/css/GridFieldPageSectionsExtension.css");
Requirements::javascript($moduleDir . "/javascript/GridFieldPageSectionsExtension.js");
+ Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true);
$id = rand(1000000, 9999999);
$field->addExtraClass("ss-gridfield-pagesections");
@@ -75,7 +75,6 @@ public function getHTMLFragments($field) {
$field->setAttribute("data-url-remove", $field->Link("remove"));
$field->setAttribute("data-url-delete", $field->Link("delete"));
$field->setAttribute("data-url-reorder", $field->Link("reorder"));
- $field->setAttribute("data-url-movetopage", $field->Link("movetopage"));
return [];
}
@@ -90,21 +89,34 @@ public function augmentColumns($gridField, &$columns) {
}
if (!in_array("Actions", $columns)) {
- array_push($columns, "Actions");
+ array_splice($columns, 2, 0, "Actions");
}
// Insert grid state initial data
$state = $gridField->getState();
if (!isset($state->open)) {
$state->open = [];
+
+ // Open all elements by default if has children
+ $list = [];
+ $newList = $gridField->getManipulatedList();
+ while (count($list) < count($newList)) {
+ foreach ($newList as $item) {
+ if ($item->isOpenByDefault() && $item->Children()->Count()) {
+ $this->openElement($state, $item);
+ }
+ }
+ $list = $newList;
+ $newList = $gridField->getManipulatedList();
+ }
}
}
public function getColumnsHandled($gridField) {
return [
"Reorder",
- "TreeNav",
"Actions",
+ "TreeNav",
];
}
@@ -129,12 +141,23 @@ public function getColumnAttributes($gridField, $record, $columnName) {
$elems[$class] = $class::getSingularName();
}
+ // if element has no parent we need to
+ // know the allowed elements of the page
+ if (!$record->_Parent) {
+ $parentClasses = $this->page->getAllowedPageElements();
+ $parentElems = [];
+ foreach ($parentClasses as $class) {
+ $parentElems[$class] = $class::getSingularName();
+ }
+ }
+
return [
- "class" => "col-treenav",
- "data-class" => $record->ClassName,
- "data-level" => strval($record->_Level),
- "data-parent" => $record->_Parent ? strval($record->_Parent->ID) : "",
- "data-allowed-elements" => json_encode($elems, JSON_UNESCAPED_UNICODE),
+ "class" => "col-treenav",
+ "data-class" => $record->ClassName,
+ "data-level" => strval($record->_Level),
+ "data-parent" => $record->_Parent ? strval($record->_Parent->ID) : "",
+ "data-allowed-parent-elements" => !$record->_Parent ? json_encode($parentElems, JSON_UNESCAPED_UNICODE) : "",
+ "data-allowed-elements" => json_encode($elems, JSON_UNESCAPED_UNICODE),
];
}
@@ -162,10 +185,10 @@ public function getColumnContent($gridField, $record, $columnName) {
$field = null;
if ($record->Children() && $record->Children()->Count() > 0) {
- $icon = ($open === true ? '▼'
- : '▶');
+ $icon = ($open === true ? ''
+ : '');
} else {
- $icon = '◼';
+ $icon = '';
}
$field = GridField_FormAction::create(
@@ -176,12 +199,19 @@ public function getColumnContent($gridField, $record, $columnName) {
["element" => $record]
);
$field->addExtraClass("level".$level . ($open ? " is-open" : " is-closed"));
+ if (!$record->Children()->Count()) {
+ $field->addExtraClass(" is-end");
+ $field->setDisabled(true);
+ }
$field->setButtonContent($icon);
$field->setForm($gridField->getForm());
return ViewableData::create()->customise([
"ButtonField" => $field,
- "Title" => $record->i18n_singular_name(),
+ "ID" => $record->ID,
+ "UsedCount" => $record->Parents()->Count() + $record->getAllPages()->Count(),
+ "ClassName" => $record->i18n_singular_name(),
+ "Title" => $record->Title,
])->renderWith("GridFieldPageElement");
}
@@ -195,7 +225,66 @@ public function getColumnContent($gridField, $record, $columnName) {
);
}
$link = Controller::join_links($gridField->link(), $link);
- return "Edit";
+ $data = new ArrayData([
+ 'Link' => $link
+ ]);
+ $editButton = $data->renderWith('SilverStripe\Forms\GridField\GridFieldEditButton');
+
+ $classes = $record->getAllowedPageElements();
+ $elems = [];
+ foreach ($classes as $class) {
+ $elems[$class] = $class::getSingularName();
+ }
+ $addButton = GridField_FormAction::create(
+ $gridField,
+ "AddAction".$record->ID,
+ null,
+ null,
+ null
+ );
+ $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE));
+ $addButton->addExtraClass("col-actions__button add-button");
+ if (!count($elems)) {
+ $addButton->setDisabled(true);
+ }
+ $addButton->setButtonContent('');
+
+ $deleteButton = GridField_FormAction::create(
+ $gridField,
+ "DeleteAction".$record->ID,
+ null,
+ null,
+ null
+ );
+ $deleteButton->setAttribute(
+ "data-used-count",
+ $record->Parents()->Count() + $record->getAllPages()->Count()
+ );
+ $deleteButton->setAttribute(
+ "data-parent-id",
+ $record->_Parent ? $record->_Parent->ID : $this->page->ID
+ );
+ $deleteButton->addExtraClass("col-actions__button delete-button");
+
+ $deleteButton->setButtonContent('');
+
+ return ViewableData::create()->customise([
+ "EditButton" => $editButton,
+ "AddButton" => $addButton,
+ "DeleteButton" => $deleteButton,
+ ])->renderWith("GridFieldPageSectionsActionColumn");
+
+ return $ret;
}
}
@@ -412,7 +501,7 @@ public function handleRemove(GridField $gridField, HTTPRequest $request) {
$parentId = intval($request->postVar("parentId"));
$parentObj = DataObject::get_by_id(PageElement::class, $parentId);
- // Detach it from this parent (from the page if we're top level)
+ // Detach it from this parent (from the page section if we're top level)
if (!$parentObj) {
$gridField->getList()->Remove($obj);
} else {
diff --git a/src/PageElement.php b/src/PageElement.php
index a46adc7..1ed8066 100755
--- a/src/PageElement.php
+++ b/src/PageElement.php
@@ -4,23 +4,28 @@
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\ClassInfo;
+use SilverStripe\Forms\FieldList;
+use SilverStripe\Forms\ReadonlyField;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use SilverStripe\Forms\GridField\GridFieldConfig;
+use SilverStripe\Forms\GridField\GridFieldConfig_Base;
use SilverStripe\Forms\GridField\GridFieldButtonRow;
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
+use SilverStripe\Forms\GridField\GridFieldEditButton;
+use SilverStripe\Forms\GridField\GridFieldFooter;
use SilverStripe\Forms\GridField\GridField;
-use SilverStripe\Forms\FieldList;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Member;
use SilverStripe\Versioned\Versioned;
use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass;
-use UncleCheese\BetterButtons\Actions\BetterButtonPrevNextAction;
-use UncleCheese\BetterButtons\Buttons\BetterButton_SaveAndClose;
-use UncleCheese\BetterButtons\Buttons\BetterButton_Save;
+use UncleCheese\BetterButtons\Actions\PrevNext;
+use UncleCheese\BetterButtons\Actions\CustomAction;
+use UncleCheese\BetterButtons\Buttons\Save;
+use UncleCheese\BetterButtons\Buttons\SaveAndClose;
class PageElement extends DataObject {
@@ -28,6 +33,7 @@ class PageElement extends DataObject {
protected static $singularName = "Element";
protected static $pluralName = "Elements";
+ protected static $defaultIsOpen = true;
public static function getSingularName() {
return static::$singularName;
@@ -35,6 +41,9 @@ public static function getSingularName() {
public static function getPluralName() {
return static::$pluralName;
}
+ public static function isOpenByDefault() {
+ return static::$defaultIsOpen;
+ }
function canView($member = null) { return true; }
function canEdit($member = null) { return true; }
@@ -87,6 +96,10 @@ function canCreate($member = null, $context = []) { return true; }
"ID",
];
+ private static $better_buttons_actions = array (
+ 'publishOnAllPages',
+ );
+
public static function getAllowedPageElements() {
$classes = array_values(ClassInfo::subclassesFor(PageElement::class));
// remove
@@ -141,7 +154,8 @@ public function getChildrenGridField() {
->addComponent($autoCompl)
->addComponent($addNewButton)
->addComponent(new GridFieldPageSectionsExtension($this->owner))
- ->addComponent(new GridFieldDetailForm());
+ ->addComponent(new GridFieldDetailForm())
+ ->addComponent(new GridFieldFooter());
$dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]);
return new GridField("Children", "Children", $this->Children(), $config);
@@ -151,6 +165,27 @@ public function getGridFieldPreview() {
return $this->Name;
}
+ // Gets all the pages that this page element is on, plus adds a __PageSection
+ // attribute to the page object so we know which section this element is in.
+ public function getAllPages() {
+ $pages = ArrayList::create();
+
+ foreach ($this->PageSections() as $section) {
+ $page = $section->Page();
+ $stage = Versioned::get_stage();
+ Versioned::set_stage(Versioned::LIVE);
+ $publishedElem = DataObject::get_by_id($section->ClassName, $section->ID)
+ ->Elements()->filter("ID", $this->ID)->First();
+ $page->__PageSection = $section;
+ $page->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version;
+ $page->__PageElementPublishedVersion = $publishedElem ? $publishedElem->Version : "Not published";
+ Versioned::set_stage($stage);
+ $pages->add($page);
+ }
+
+ return $pages;
+ }
+
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Pages');
@@ -162,6 +197,38 @@ public function getCMSFields() {
$fields->addFieldToTab('Root.PageSections', $this->getChildrenGridField());
}
+ // Add our newest version as a readonly field
+ $fields->addFieldsToTab(
+ "Root.Main",
+ ReadonlyField::create("Version", "Version", $this->Version),
+ "Title"
+ );
+
+ // Create an array of all the pages this element is on
+ $pages = $this->getAllPages();
+
+ if ($pages->Count() > 0) {
+ // Remove default field
+ $fields->removeByName("PageSections");
+
+ $config = GridFieldConfig_Base::create()
+ ->removeComponentsByType(GridFieldDataColumns::class)
+ ->addComponent($dataColumns = new GridFieldDataColumns())
+ ->addComponent(new GridFieldDetailForm())
+ ->addComponent(new GridFieldEditButton());
+ $dataColumns->setDisplayFields([
+ "ID" => "ID",
+ "ClassName" => "Type",
+ "Title" => "Title",
+ "__PageSection.Name" => "PageSection",
+ "__PageElementVersion" => "Element version",
+ "__PageElementPublishedVersion" => "Published element version",
+ "getPublishState" => "Page state",
+ ]);
+ $gridField = GridField::create("Pages", "Pages", $pages, $config);
+ $fields->addFieldToTab("Root.Pages", $gridField);
+ }
+
return $fields;
}
@@ -194,4 +261,27 @@ public function forTemplate($parentList = "") {
["ParentList" => $parentList, "Parents" => $parents, "Page" => $page]
);
}
+
+ public function getBetterButtonsUtils() {
+ $fieldList = FieldList::create([
+ PrevNext::create(),
+ ]);
+ return $fieldList;
+ }
+
+ public function getBetterButtonsActions() {
+ $actions = FieldList::create([
+ SaveAndClose::create(),
+ CustomAction::create('publishOnAllPages', 'Publish on all pages')
+ ->setRedirectType(CustomAction::REFRESH)
+ ]);
+ return $actions;
+ }
+
+ public function publishOnAllPages() {
+ foreach ($this->getAllPages() as $page) {
+ $page->publish(Versioned::get_stage(), Versioned::LIVE);
+ }
+ return 'Published on all pages';
+ }
}
diff --git a/src/PageSection.php b/src/PageSection.php
index dd3e489..e47e718 100644
--- a/src/PageSection.php
+++ b/src/PageSection.php
@@ -58,7 +58,7 @@ public function onBeforeWrite() {
public function onAfterWrite() {
parent::onAfterWrite();
- if (Versioned::get_stage() == Versioned::DRAFT) {
+ if (!$this->__isNew && Versioned::get_stage() == Versioned::DRAFT) {
$this->Page()->__PageSectionCounter++;
$this->Page()->write();
}
@@ -85,4 +85,15 @@ public function forTemplate() {
return $form->forTemplate();
}
+
+ // Gets the name of this section from the page it is on
+ public function getName() {
+ $page = $this->Page();
+ foreach ($page->getPageSectionNames() as $sectionName) {
+ if ($page->{"PageSection" . $sectionName . "ID"} === $this->ID) {
+ return $sectionName;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php
index ecf3c46..82734ab 100755
--- a/src/PageSectionsExtension.php
+++ b/src/PageSectionsExtension.php
@@ -14,6 +14,7 @@
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\ORM\SiteTree;
use SilverStripe\ORM\DataExtension;
+use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\Versioned\Versioned;
use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass;
@@ -38,6 +39,11 @@ public static function get_extra_config($class = null, $extensionClass = null) {
$owns[] = $name;
$cascade_deletes[] = $name;
+
+ // Add the inverse relation to the PageElement class
+ /*Config::inst()->update(PageElement::class, "versioned_belongs_many_many", array(
+ $class . "_" . $name => $class . "." . $name
+ ));*/
}
// Create the relations for our sections
@@ -55,6 +61,14 @@ public static function getAllowedPageElements() {
return $classes;
}
+ public function getPageSectionNames() {
+ $sections = Config::inst()->get($this->owner->ClassName, "page_sections", Config::EXCLUDE_EXTRA_SOURCES);
+ if (!$sections) {
+ $sections = ["Main"];
+ }
+ return $sections;
+ }
+
public function onBeforeWrite() {
parent::onBeforeWrite();
@@ -71,6 +85,7 @@ public function onBeforeWrite() {
if (!$this->owner->$name()->ID) {
$section = PageSection::create();
$section->PageID = $this->owner->ID;
+ $section->__isNew = true;
$section->write();
$this->owner->$name = $section;
}
@@ -119,4 +134,8 @@ public function RenderPageSection($name = "Main") {
["Elements" => $elements, "ParentList" => strval($this->owner->ID)]
);
}
+
+ public function getPublishState() {
+ return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft");
+ }
}
diff --git a/templates/GridFieldPageElement.ss b/templates/GridFieldPageElement.ss
index 471c3b8..1b87b4c 100755
--- a/templates/GridFieldPageElement.ss
+++ b/templates/GridFieldPageElement.ss
@@ -1 +1,11 @@
-$ButtonField $Title
+
+ $ButtonField
+
+
+ $ClassName (ID: {$ID}, {$UsedCount}x)
+
+
+ $Title
+
+
+
diff --git a/templates/GridFieldPageSectionsActionColumn.ss b/templates/GridFieldPageSectionsActionColumn.ss
new file mode 100644
index 0000000..75edf74
--- /dev/null
+++ b/templates/GridFieldPageSectionsActionColumn.ss
@@ -0,0 +1,5 @@
+
+ $AddButton
+ $DeleteButton
+ $EditButton
+
diff --git a/templates/PageElement.ss b/templates/PageElement.ss
new file mode 100644
index 0000000..d7a3c0d
--- /dev/null
+++ b/templates/PageElement.ss
@@ -0,0 +1,7 @@
+
+
$Title (v$Version)
+ $Layout
+
+ $RenderChildren($ParentList)
+
+