diff --git a/assets/css/relationship.css b/assets/css/relationship.css new file mode 100755 index 0000000..c9a38bc --- /dev/null +++ b/assets/css/relationship.css @@ -0,0 +1,112 @@ +/* Field wrapper: */ +.relationship-field { + display: flex; + flex-wrap: wrap; + border: 1px solid #ddd; + background-color: white; +} + +/* Default styling for both lists: */ +.relationship-list { + flex: 1 0 200px; + overflow-x: hidden; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + height: 13em; + margin: 0; + padding: 0.5em; + border: 1px solid #ddd; + background-color: white; + list-style: none; +} + +/* The list with all unselected items should be hidden: */ +.relationship-list--unselected { + display: none; +} + +/* Reset native button styles: */ +.relationship-list button { + border: none; + background: none; + margin: 0; + padding: 0; + color: inherit; +} + +.relationship-list button:not([disabled]) { + cursor: pointer; +} + +/* Default styling for all list items: */ +.relationship-list--available button, +.relationship-list--selected li { + position: relative; + padding: 5px 2em 5px 7px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* List with available items: */ +.relationship-list--available { + background-color: #f7f7f7; +} + +.relationship-list--available button { + display: block; + width: 100%; + text-align: left; +} + +.relationship-list--available button:not([disabled]):hover, +.relationship-list--available button:not([disabled]):focus { + background-color: rgba(0, 0, 0, 0.075); +} + +.relationship-list--available button[disabled] { + color: #777; +} + +.relationship-list--available .icon-add { + position: absolute; + right: 0.5em; +} + +.relationship-list--available button[disabled] .icon-add { + display: none; +} + +/* Selected list items: */ +.relationship-list--selected li { + padding-right: 2em; + cursor: move; +} + +.relationship-list--selected li input[type="checkbox"] { + display: none; +} + +/* List item during sorting: */ +.relationship-list--selected li.ui-sortable-helper { + background-color: white; + border-radius: 2px; + box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.1); + transition: box-shadow 150ms ease-out; +} + +/* Placeholder item when sorting: */ +.relationship-list--selected li.ui-sortable-placeholder { + background-color: rgba(0, 0, 0, 0.025); +} + +/* Delete icon: */ +.relationship-list--selected li .icon-delete { + position: absolute; + right: 0.5em; +} + +/* Sort icon: */ +.relationship-list--selected li .icon-left { + color: #ccc; +} diff --git a/assets/js/relationship.js b/assets/js/relationship.js new file mode 100755 index 0000000..e77acd2 --- /dev/null +++ b/assets/js/relationship.js @@ -0,0 +1,97 @@ +(function($) { + + var Relationship = function(field) { + + // Get references to the item lists: + var $list_available = $(field).find('.relationship-list--available'); + var $list_selected = $(field).find('.relationship-list--selected'); + var $list_unselected = $(field).find('.relationship-list--unselected'); + + /** + * Add an item to the selection on click: + */ + $list_available.on('click', 'button:not([disabled])', function(event) { + event.preventDefault(); + + // Clicked item should be disabled: + $(this).prop('disabled', true); + + // Get key of clicked item: + var key = $(this).data('key'); + + // Find item in unselected list: + var $selected_item = $list_unselected.find('li[data-key="' + key + '"]'); + + // Move the selected item to the end of the selected list: + $selected_item.appendTo($list_selected); + + // Set the checkbox as checked: + $selected_item.find('input').prop('checked', true); + + // Notify Kirby that some changes are made: + $selected_item.find('input').trigger('change'); + + // Scroll to bottom of the list to show the new item: + $list_selected.stop().delay(20).animate({ + scrollTop: $list_selected[0].scrollHeight + }, { + duration: 600 + }); + }); + + /** + * Remove a selected item on click: + */ + $list_selected.on('click', 'button', function(event) { + event.preventDefault(); + + // Get a reference of the item to be removed from the selection: + var $selected_item = $(this).closest('li'); + + // Move the selected item to the unselected list: + $selected_item.appendTo($list_unselected); + + // Set the checkbox as unchecked: + $selected_item.find('input').prop('checked', false); + + // Notify Kirby that some changes are made: + $selected_item.find('input').trigger('change'); + + // Get the key of the selected item: + var key = $selected_item.data('key'); + + // Make the selected item available again in the available list: + $list_available.find('button[data-key="' + key + '"]').prop('disabled', false); + }); + + /** + * Make the list sortable using jQuery Sortable library + * Docs: http://api.jqueryui.com/sortable + */ + $list_selected.sortable({ + revert: 100, + placeholder: 'ui-sortable-placeholder', + forcePlaceholderSize: true, + update: function(event, ui) { + // Notify Kirby that some changes are made: + ui.item.find('input').trigger('change'); + } + }); + }; + + /** + * Initialize the field: + */ + $.fn.relationship = function() { + return this.each(function() { + if ($(this).data('relationship')) { + return $(this); + } else { + var relationship = new Relationship(this); + $(this).data('relationship', relationship); + return $(this); + } + }); + }; + +})(jQuery); diff --git a/changelog.md b/changelog.md new file mode 100755 index 0000000..6b0b613 --- /dev/null +++ b/changelog.md @@ -0,0 +1,4 @@ +# Changelog + +## 1.0.0 (2017-08-11) +- Initial version diff --git a/package.json b/package.json new file mode 100755 index 0000000..27fddcb --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "relationship", + "description": "Sortable multiselect field for Kirby 2 CMS", + "author": "Ola Christensson ", + "version": "1.0.0", + "type": "kirby-field", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/olach/kirby-relationship" + } +} diff --git a/readme.md b/readme.md new file mode 100755 index 0000000..f116401 --- /dev/null +++ b/readme.md @@ -0,0 +1,123 @@ +# Kirby Relationship field + +The Relationship field allows you to select and sort multiple items from a list. Think of it as a sortable multiselect field or a sortable checkboxes field. + +![relationship-field-demo](https://user-images.githubusercontent.com/1300644/29208814-e1f92692-7e8b-11e7-857f-b646853d3ed8.gif) + +## Requirements +This field has been tested with Kirby 2.5+, but it should probably work with earlier versions too. + +## Installation +### Manually +[Download the files](https://github.com/olach/kirby-relationship/archive/master.zip) and place them inside `site/fields/relationship/`. + +### With Kirby CLI +Kirby's [command line interface](https://github.com/getkirby/cli) makes the installation really simple: + + $ kirby plugin:install olach/kirby-relationship + +Updating is also easy: + + $ kirby plugin:update olach/kirby-relationship + +## Usage +The field is an extension of the [Checkboxes field](https://getkirby.com/docs/cheatsheet/panel-fields/checkboxes). All options of that field apply to this field too. The data is saved as a comma separated string, which means that this field is interchangeable with the Checkboxes field. + +### Example with predefined options +#### Blueprint + +```yaml +countries: + label: Countries + type: relationship + options: + sweden: Sweden + norway: Norway + denmark: Denmark + finland: Finland + iceland: Iceland + germany: Germany + france: France + spain: Spain + portugal: Portugal +``` + +#### Template + +```php + +``` + +### Example with related pages +#### Blueprint + +```yaml +related: + label: Related articles + type: relationship + options: query + query: + fetch: siblings +``` + +#### Template + +```php +

Related articles

+ +``` + +## Extra features + +### Controller: +This field is extended with an option to use a user specified function to have even more control of the options that will be loaded. The idea is taken from the [Controlled List plugin](https://github.com/rasteiner/controlledlist). + +#### Example +Create a simple plugin that lets you choose from the panel users. + +`site/plugins/myplugin/myplugin.php`: + +```php +class MyPlugin { + static function userlist($field) { + $kirby = kirby(); + $site = $kirby->site(); + $users = $site->users(); + + $result = array(); + + foreach ($users as $user) { + $result[$user->username] = $user->firstName() . ' ' . $user->lastName(); + } + + return $result; + } +} +``` + +In your blueprint: + +```yaml +users: + label: Users + type: relationship + controller: MyPlugin::userlist +``` + +## Version history +You can find the version history in the [changelog](changelog.md). + +## License +[MIT License](http://www.opensource.org/licenses/mit-license.php) diff --git a/relationship.php b/relationship.php new file mode 100755 index 0000000..e30350b --- /dev/null +++ b/relationship.php @@ -0,0 +1,34 @@ + array( + 'relationship.js' + ), + 'css' => array( + 'relationship.css' + ) + ); + + /** + * This field can load a user specified function if set in the blueprint. + */ + public function options() { + if ($this->controller()) { + return call_user_func($this->controller(), $this); + } else { + return parent::options(); + } + } + + /** + * Use a template file to build all html. + */ + public function content() { + return tpl::load(__DIR__ . DS . 'template.php', array('field' => $this)); + } + +} diff --git a/template.php b/template.php new file mode 100755 index 0000000..aa68dc0 --- /dev/null +++ b/template.php @@ -0,0 +1,57 @@ +options(); // All available options +$values = $field->value(); // All selected options + +// "$field->options()" contains both the key and the value. E.g. the page url and title. +// "$field->value()" contains only the key. E.g. only the page url. +// Make a new array with selected options stored with both the key and value: +$selected_options = []; +foreach ($values as $value) { + $selected_options[$value] = $all_options[$value]; +} + +// We also need an array with all unselected options. +// Array_diff will do the trick: +$unselected_options = array_diff($all_options, $selected_options); +?> + +
+
+ +
    + $value): ?> +
  • + +
  • + +
+ +
    + $value): ?> +
  • + + + + +
  • + +
+ +
    + $value): ?> +
  • + + + + +
  • + +
+ +
+