├── .gitignore ├── LICENSE ├── README.md ├── dashcols ├── DashColsPlugin.php ├── controllers │ └── DashCols_LayoutsController.php ├── migrations │ ├── m150212_090452_dashcols_change_hiddenfields_column.php │ ├── m150402_144345_dashcols_addMetaFieldsColumn.php │ ├── m151002_144500_dashcols_addUserGroupIdColumn.php │ └── m151103_153210_dashcols_addAssetSourceIdColumn.php ├── models │ └── DashCols_LayoutModel.php ├── records │ └── DashCols_LayoutRecord.php ├── resources │ ├── css │ │ ├── dashcols.cp.css │ │ └── dashcols.index.css │ └── js │ │ ├── dashcols.cp.js │ │ └── dashcols.index.js ├── services │ ├── DashColsService.php │ ├── DashCols_AttributesService.php │ ├── DashCols_FieldsService.php │ └── DashCols_LayoutsService.php ├── templates │ ├── _layouts │ │ ├── _edit.twig │ │ └── index.twig │ ├── _legacy.twig │ ├── _partials │ │ └── _footer.twig │ ├── about.twig │ └── settings.twig └── variables │ └── DashColsVariable.php └── source ├── demo ├── cp.jpg ├── index.jpg └── sort.jpg ├── src ├── scripts │ ├── dashcols.cp.js │ └── dashcols.index.js └── styles │ ├── dashcols.cp.scss │ └── dashcols.index.scss └── tasks ├── Gruntfile.js ├── grunt-configs ├── autoprefixer.js ├── clean.js ├── copy.js ├── cssmin.js ├── growl.js ├── jshint.js ├── sass.js ├── scsslint.js ├── uglify.js └── watch.js ├── package.json └── resources ├── jshint_conf.json └── scsslint_conf.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | !.gitignore 5 | 6 | .ssh/ 7 | _backup/ 8 | 9 | *.sassc 10 | .sass-cache 11 | .DS_Store 12 | 13 | db-sync 14 | tmp 15 | 16 | # Compiled source # 17 | ################### 18 | *.com 19 | *.class 20 | *.dll 21 | *.exe 22 | *.o 23 | *.so 24 | */.sass-cache/* 25 | 26 | # Packages # 27 | ############ 28 | # it's better to unpack these files and commit the raw source 29 | # git has its own built in compression methods 30 | *.7z 31 | *.dmg 32 | *.gz 33 | *.iso 34 | *.jar 35 | *.rar 36 | *.tar 37 | *.zip 38 | *.sublime-project 39 | *.sublime-workspace 40 | 41 | # Logs and databases # 42 | ###################### 43 | *.log 44 | 45 | # OS generated files # 46 | ###################### 47 | .DS_Store 48 | .DS_Store? 49 | ._* 50 | .Spotlight-V100 51 | .Trashes 52 | Icon? 53 | ehthumbs.db 54 | Thumbs.db 55 | 56 | 57 | # Project # 58 | ###################### 59 | 60 | node_modules/ 61 | bower_components/ 62 | /vendor/ 63 | composer.lock 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 M. Mikkel Rummelhoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important notice 2 | 3 | Pixel and Tonic recently unveiled Craft 2.5, currently in public beta and due to release December 1st 2015. Among a slew of great additions, this update adds _Customizable Element Indexes_ to Craft – basically making the DashCols plugin redundant. 4 | 5 | The native Customizable Element Indexes are awesome, and finally having this functionality in core is a great boon for Craft, its users and the community. 6 | 7 | DashCols 1.3 will be the final release for DashCols, and it will not receive any support, new features or bug fixes going forward. This repo will stay up indefinetely, though. 8 | 9 | Thanks a lot for all the support and interest in this plugin! 10 | 11 | [Read more about Craft 2.5 on Pixel & Tonic's blog](http://pixelandtonic.com/blog/craft-2.5-beta) 12 | 13 | **Update February 2nd 2016** 14 | Some people have lamented losing DashCol's sorting features. I concur that the current CEI implementation leaves something to be desired in regards to _sorting_ on custom field columns, so I decided to build a standalone plugin called [CP Sortable Custom Columns](https://github.com/mmikkel/CpSortableCustomColumns-Craft), which – like DashCols – will a) make the sortable menu in index tables _source aware_ and b) automatically add any sortable custom field included in an index table to that table's sortable attributes. 15 | 16 | – Mats 17 | 18 | *** 19 | 20 | # DashCols Craft CMS Plugin v. 1.3 21 | 22 | _Now with Users and Assets support!_ 23 | 24 | **The element index tables lists all your content, but let's face it – they're kind of sparse. DashCols makes it easy to add (almost) any custom field to your entry, asset, category and user control panel tables.** 25 | 26 | ![Screenshot of index table customized using DashCols](/source/demo/index.jpg?raw=true "Index table customized w/ DashCols") 27 | 28 | In addition, DashCols will also: 29 | 30 | * Enable you to show/hide _default columns_ (URI, section, expiry date etc.) and _element metadata_ (ID, author and last updated date ++) 31 | * Enable you to _sort_ index tables on most columns 32 | * Improve the responsiveness of your index tables 33 | 34 | ## Installation and setup 35 | 36 | * Download & unzip 37 | * Move the /dashcols folder to craft/plugins 38 | * Install 39 | 40 | After installing, visit _DashCols’_ CP section and use the built-in Field Layout Designer to add custom fields to entry, category, asset or user sources, and configure the output of default columns and/or element metadata. 41 | 42 | Please note that not all FieldTypes are supported – look below for the complete list. 43 | 44 | ## Options 45 | 46 | * Undercover mode: You want to keep DashCols running, but completely hide the nav tab and "Edit columns" button. 47 | 48 | ![CP section](/source/demo/cp.jpg?raw=true) 49 | 50 | ### Supported FieldTypes 51 | 52 | * Assets 53 | * Categories 54 | * Checkboxes 55 | * Color 56 | * Date/time 57 | * Dropdown 58 | * Entries 59 | * Lightswitch 60 | * Multi-select 61 | * Number 62 | * Plain Text 63 | * Position Select 64 | * Radio Buttons 65 | * Tags 66 | * Users 67 | 68 | ### Supported Custom FieldTypes 69 | 70 | * Address (Smart Map) by @lindseydiloreto 71 | * Doxter by @selvinortiz 72 | * Preparse Field by @aelvan 73 | 74 | ### Unsupported FieldTypes 75 | 76 | * Matrix 77 | * Rich Text 78 | * Table 79 | 80 | ### …but what about my awesome _custom_ FieldType? 81 | 82 | For now, any String based attribute will display more or less as-is (some formatting is applied to stuff like URLs, Hex color codes etc.), and string values are truncated to a maximum of 50 characters. More complex stuff needs to be built in; I’m currently exploring options for enabling users to easily add support for their own custom FieldTypes. 83 | 84 | If you have a publicly available FieldType plugin you wish to see supported, please file a feature request! 85 | 86 | ### Roadmap 87 | 88 | Look for the following in coming updates: 89 | 90 | * Support for popular, custom FieldTypes 91 | * Option to clear a layout w/ a single button 92 | 93 | ## Bugs, feature requests, support 94 | 95 | Please file any bug reports or other requests at GitHub: [https://github.com/mmikkel/dashcols-craft/issues](https://github.com/mmikkel/dashcols-craft/issues) 96 | 97 | Note that _DashCols_ is a hobby project – unfortunately I can’t make any promises regarding response time for any requests. 98 | 99 | **Pull requests are very welcome!** 100 | 101 | ## Disclaimer 102 | 103 | _DashCols_ is provided free of charge. The author is not responsible for any data loss or other problems resulting from the use of this plugin. 104 | 105 | Please report any bugs, feature requests or other issues [here](https://github.com/mmikkel/dashcols-craft/issues). Note that _DashCols_ is a hobby project and I can offer no promises regarding response time, feature implementations or bug amendments. 106 | 107 | ### Changelog 108 | 109 | #### 1.3 110 | 111 | * Now supports editing columns for _Users_ – big thanks to Lindsey DiLoreto for the help! 112 | * Now supports editing columns for _Assets_ 113 | * CP section redesigned 114 | * Fixes issue with the _Edit columns_ button not always appearing 115 | * Various small improvements and fixes 116 | 117 | #### 1.2.5 118 | 119 | * Fixed issue where numeric columns wouldn't render zeros. Fixes #24 120 | 121 | #### 1.2.4 122 | 123 | * Added support for Doxter 124 | * Slightly improved support for complex custom fieldtypes in general 125 | * Removed custom plugin name setting 126 | 127 | #### 1.2.3 128 | 129 | * SVG assets now display as thumbnails for Craft 2.4 builds (only where ImageMagick is installed) 130 | 131 | #### 1.2.2 132 | 133 | * Fixed bug where the _Structure_ sorting option was hidden 134 | 135 | #### 1.2.1 136 | 137 | * **Added Entry Type metadata column** 138 | 139 | #### 1.2 140 | 141 | * **Added sorting capabilities for FieldTypes of Boolean, String, Number or DateTime value** 142 | 143 | #### 1.1.9 144 | 145 | * **Added option to output element metadata (Updated Date, ID and Author) as columns** 146 | * Fixed issue where string values "1" and "0" would always render as a Lightswitch attribute – thanks Fred, you're a champ. 147 | * Fixed issue where string values starting with "#" would sometimes render as a Color attribute 148 | * String values interpreted as external links will now open in a new tab 149 | * Added setting for renaming _DashCols_ 150 | 151 | #### 1.1.8 152 | 153 | * _Asset_ columns will now display total number of files (if more than 1) 154 | * _Asset_ columns now display icon + filename for files. 155 | 156 | #### 1.1.7 157 | 158 | Fixed issue where layouts would not save if CSRF is enabled 159 | 160 | #### 1.1.6 161 | 162 | _Minor refactor_ 163 | 164 | #### 1.1.5 165 | 166 | * Fixed issue where the Edit Columns button would not be added to non-managed index tables 167 | * Date/Time columns now display date and/or time based on field settings 168 | 169 | #### 1.1.4 170 | 171 | * Added settings page and the _Undercover mode_ setting to hide the CP section and disable layout editing 172 | 173 | #### 1.1.3 174 | 175 | * Fixed issue w/ redirect on All entries layout save 176 | * Edit template only displays relevant default fields 177 | * Added footer to CP section 178 | * Removed editing for individual Single sections 179 | 180 | #### 1.1.2 181 | 182 | * Fixed issue with Singles layout redirect on save 183 | 184 | #### 1.1.1 185 | 186 | * Hotfixed an annoying issue w/ move icons not being vertically centered in tall table rows 187 | 188 | #### 1.1 189 | 190 | * Added option to hide default fields (postDate, expiryDate, URI and section) 191 | * (Hopefully) improved CP section sub nav 192 | * DashCols now redirects to index table upon saving a layout 193 | * Some minor CSS fixes here and there 194 | 195 | #### 1.0 196 | 197 | Initial public release. 198 | -------------------------------------------------------------------------------- /dashcols/DashColsPlugin.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/DashCols-Craft 12 | */ 13 | 14 | class DashColsPlugin extends BasePlugin 15 | { 16 | 17 | protected $_version = '1.3', 18 | $_developer = 'Mats Mikkel Rummelhoff', 19 | $_developerUrl = 'http://mmikkel.no', 20 | $_pluginName = 'DashCols', 21 | $_pluginUrl = 'https://github.com/mmikkel/DashCols-Craft', 22 | $_minVersion = '2.3'; 23 | 24 | public function getName() 25 | { 26 | return $this->_pluginName; 27 | } 28 | 29 | public function getVersion() 30 | { 31 | return $this->_version; 32 | } 33 | 34 | public function getDeveloper() 35 | { 36 | return $this->_developer; 37 | } 38 | 39 | public function getDeveloperUrl() 40 | { 41 | return $this->_developerUrl; 42 | } 43 | 44 | public function getPluginUrl() 45 | { 46 | return $this->_pluginUrl; 47 | } 48 | 49 | public function hasCpSection() 50 | { 51 | return craft()->dashCols->isCpSectionDisabled() ? false : true; 52 | } 53 | 54 | protected function defineSettings() 55 | { 56 | return array( 57 | 'cpSectionDisabled' => array(AttributeType::Bool, 'default' => false), 58 | ); 59 | } 60 | 61 | public function getSettingsHtml() 62 | { 63 | return craft()->templates->render('dashcols/settings', array( 64 | 'settings' => $this->getSettings(), 65 | )); 66 | } 67 | 68 | public function registerCpRoutes() 69 | { 70 | 71 | if (!$this->isCraftRequiredVersion()) return array( 72 | 'dashcols' => array('action' => 'dashCols/layouts/getIndex'), 73 | ); 74 | 75 | return array( 76 | 77 | 'dashcols' => array('action' => 'dashCols/layouts/getIndex'), 78 | 79 | // Entries 80 | 'dashcols/entries(/(?P[-\w]+))?(/(?P[-\w]+))?' => array('action' => 'dashCols/layouts/editEntriesLayout'), 81 | 82 | // Category group 83 | 'dashcols/categories(/(?P[-\w]+))?' => array('action' => 'dashCols/layouts/editCategoryGroupLayout'), 84 | 85 | // User groups 86 | 'dashcols/users(/(?P[-\w]+))?' => array('action' => 'dashCols/layouts/editUserGroupLayout'), 87 | 88 | // Asset sources 89 | 'dashcols/assets(/(?P[-\w]+))?' => array('action' => 'dashCols/layouts/editAssetSourceLayout'), 90 | 91 | // Todo: Add custom element types here 92 | 93 | ); 94 | } 95 | 96 | public function init () { 97 | 98 | parent::init(); 99 | 100 | if (!craft()->request->isCpRequest() || !craft()->userSession->getUser()) { 101 | return false; 102 | } 103 | 104 | $this->includeResources(); 105 | 106 | if ($this->isCraftRequiredVersion()) craft()->dashCols_layouts->init(); 107 | 108 | } 109 | 110 | public function getCraftRequiredVersion() 111 | { 112 | return $this->_minVersion; 113 | } 114 | 115 | public function isCraftRequiredVersion() 116 | { 117 | return version_compare(craft()->getVersion(), $this->getCraftRequiredVersion(), '>='); 118 | } 119 | 120 | protected function includeResources() 121 | { 122 | 123 | if (craft()->request->isAjaxRequest()) { 124 | return false; 125 | } 126 | 127 | $segments = craft()->request->segments; 128 | 129 | if (!is_array($segments) || empty($segments)) { 130 | return false; 131 | } 132 | 133 | $elementIndexes = array('entries', 'categories', 'users', 'assets'); 134 | 135 | // Todo: Add custom element types here 136 | 137 | if (in_array($segments[0], $elementIndexes)) 138 | { 139 | // Index tables 140 | craft()->templates->includeCssResource('dashcols/css/dashcols.index.css'); 141 | craft()->templates->includeJsResource('dashcols/js/dashcols.index.js'); 142 | } 143 | else if ($segments[0] === 'dashcols') 144 | { 145 | // DashCols' CP section 146 | craft()->templates->includeCssResource('dashcols/css/dashcols.cp.css'); 147 | craft()->templates->includeJsResource('dashcols/js/dashcols.cp.js'); 148 | } 149 | else 150 | { 151 | return false; 152 | } 153 | 154 | $settings = json_encode($this->getSettings()->attributes); 155 | craft()->templates->includeJs('window._DashCols='.$settings.';'); 156 | 157 | } 158 | 159 | /* 160 | * Modify index table attributes 161 | * 162 | */ 163 | public function modifyEntryTableAttributes(&$attributes, $source) 164 | { 165 | craft()->dashCols_layouts->setLayoutFromEntrySource($source); 166 | craft()->dashCols_attributes->modifyIndexTableAttributes($attributes); 167 | } 168 | 169 | public function modifyCategoryTableAttributes(&$attributes, $source) 170 | { 171 | craft()->dashCols_layouts->setLayoutFromCategorySource($source); 172 | craft()->dashCols_attributes->modifyIndexTableAttributes($attributes); 173 | } 174 | 175 | public function modifyAssetTableAttributes(&$attributes, $source) 176 | { 177 | craft()->dashCols_layouts->setLayoutFromAssetSource($source); 178 | craft()->dashCols_attributes->modifyIndexTableAttributes($attributes); 179 | } 180 | 181 | public function modifyUserTableAttributes(&$attributes, $source) 182 | { 183 | craft()->dashCols_layouts->setLayoutFromUserSource($source); 184 | craft()->dashCols_attributes->modifyIndexTableAttributes($attributes); 185 | } 186 | 187 | /* 188 | * Modify sortable attributes 189 | * 190 | */ 191 | public function modifyEntrySortableAttributes(&$attributes) 192 | { 193 | craft()->dashCols_attributes->modifyIndexSortableAttributes($attributes); 194 | } 195 | 196 | public function modifyCategorySortableAttributes(&$attributes) 197 | { 198 | craft()->dashCols_attributes->modifyIndexSortableAttributes($attributes); 199 | } 200 | 201 | public function modifyAssetSortableAttributes(&$attributes) 202 | { 203 | craft()->dashCols_attributes->modifyIndexSortableAttributes($attributes); 204 | } 205 | 206 | public function modifyUserSortableAttributes(&$attributes) 207 | { 208 | craft()->dashCols_attributes->modifyIndexSortableAttributes($attributes); 209 | } 210 | 211 | /* 212 | * Get table attribute HTML 213 | * 214 | */ 215 | public function getEntryTableAttributeHtml(EntryModel $entry, $attribute) 216 | { 217 | return craft()->dashCols_attributes->getAttributeHtml($entry, $attribute); 218 | } 219 | 220 | public function getCategoryTableAttributeHtml(CategoryModel $category, $attribute) 221 | { 222 | return craft()->dashCols_attributes->getAttributeHtml($category, $attribute); 223 | } 224 | 225 | public function getAssetTableAttributeHtml(AssetFileModel $asset, $attribute) 226 | { 227 | return craft()->dashCols_attributes->getAttributeHtml($asset, $attribute); 228 | } 229 | 230 | public function getUserTableAttributeHtml(UserModel $user, $attribute) 231 | { 232 | return craft()->dashCols_attributes->getAttributeHtml($user, $attribute); 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /dashcols/controllers/DashCols_LayoutsController.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashCols_LayoutsController extends BaseController 15 | { 16 | 17 | /** 18 | * @access public 19 | * @return mixed 20 | */ 21 | public function actionGetIndex(array $variables = array()) 22 | { 23 | 24 | if (craft()->dashCols->isCpSectionDisabled()) { 25 | throw new HttpException(404); 26 | } 27 | 28 | $this->requireCraftVersion(); 29 | 30 | // Get layout targets 31 | $variables['channels'] = craft()->dashCols->getChannels(); 32 | $variables['structures'] = craft()->dashCols->getStructures(); 33 | $variables['categoryGroups'] = craft()->dashCols->getCategoryGroups(); 34 | $variables['userGroups'] = craft()->dashCols->getUserGroups(); 35 | $variables['assetSources'] = craft()->dashCols->getAssetSources(); 36 | 37 | // Get tabs 38 | $variables['tabs'] = craft()->dashCols->getCpTabs(); 39 | $variables['selectedTab'] = 'dashColsIndex'; 40 | 41 | // Render 42 | return $this->renderTemplate('dashCols/_layouts', $variables); 43 | 44 | } 45 | 46 | /** 47 | * @access public 48 | * @return mixed 49 | */ 50 | public function actionEditEntriesLayout(array $variables = array()) 51 | { 52 | 53 | $this->requireCraftVersion(); 54 | 55 | // Get tab nav items (all sections) 56 | $variables['tabNav'] = array( 57 | array( 58 | 'name' => Craft::t('All entries'), 59 | 'url' => 'dashcols/entries', 60 | ), 61 | array( 62 | 'name' => Craft::t('Singles'), 63 | 'url' => 'dashcols/entries/singles', 64 | ), 65 | ); 66 | $allSections = craft()->dashCols->getSections(); 67 | if ($allSections) 68 | { 69 | foreach ($allSections as $section) 70 | { 71 | $variables['tabNav'][] = array( 72 | 'name' => $section->name, 73 | 'handle' => $section->handle, 74 | 'url' => 'dashcols/entries/section/' . $section->handle, 75 | ); 76 | } 77 | } 78 | 79 | // Set selected tab 80 | $variables['selectedTab'] = 'entries'; 81 | 82 | if (!isset($variables['sourceHandle'])) $variables['sourceHandle'] = 'entries'; 83 | 84 | if (!isset($variables['sectionHandleOrId'])) 85 | { 86 | $variables['listingHandle'] = $variables['sourceHandle']; 87 | return $this->actionEditListingLayout($variables); 88 | } 89 | 90 | // Get section 91 | if (!isset($variables['sectionHandleOrId'])) throw new HttpException(404); 92 | $section = craft()->dashCols->getSectionByHandleOrId($variables['sectionHandleOrId']); 93 | if (!$section) throw new HttpException(404); 94 | $variables['section'] = $section; 95 | 96 | // Get layout model 97 | $variables['layout'] = craft()->dashCols_layouts->getLayoutBySectionId($section->id); 98 | if (!$variables['layout']) $variables['layout'] = new DashCols_LayoutModel(); 99 | 100 | // Breadcrumb 101 | $variables['crumb'] = array( 102 | 'label' => Craft::t($section->name), 103 | 'url' => UrlHelper::getUrl('dashcols/entries/section/' . $section->handle), 104 | ); 105 | 106 | // Get default + meta fields 107 | $variables['defaultFields'] = craft()->dashCols_fields->getDefaultFields('entries'); 108 | $variables['metaFields'] = craft()->dashCols_fields->getMetaFields('entries'); 109 | 110 | // Get redirect URL 111 | $variables['redirectUrl'] = UrlHelper::getUrl('entries/' . $section->handle); 112 | 113 | return $this->renderEditLayout($variables); 114 | 115 | } 116 | 117 | /** 118 | * @access public 119 | * @return mixed 120 | */ 121 | public function actionEditCategoryGroupLayout(array $variables = array()) 122 | { 123 | 124 | $this->requireCraftVersion(); 125 | 126 | if (!isset($variables['categoryGroupHandleOrId'])) { 127 | $categoryGroups = craft()->dashCols->getCategoryGroups(); 128 | if (!$categoryGroups) throw new HttpException(404); 129 | craft()->request->redirect(UrlHelper::getUrl('dashcols/categories/' . $categoryGroups[0]->handle)); 130 | exit(); 131 | } 132 | 133 | // Get tab nav items (all category groups) 134 | $allCategoryGroups = craft()->dashCols->getCategoryGroups(); 135 | $variables['tabNav'] = array(); 136 | if ($allCategoryGroups) 137 | { 138 | foreach ($allCategoryGroups as $categoryGroup) 139 | { 140 | $variables['tabNav'][] = array( 141 | 'name' => $categoryGroup->name, 142 | 'handle' => $categoryGroup->handle, 143 | 'url' => 'dashcols/categories/' . $categoryGroup->handle, 144 | ); 145 | } 146 | } 147 | 148 | // Set selected tab 149 | $variables['selectedTab'] = 'categories'; 150 | 151 | // Get category group 152 | $categoryGroup = craft()->dashCols->getCategoryGroupByHandleOrId($variables['categoryGroupHandleOrId']); 153 | if (!$categoryGroup) throw new HttpException(404); 154 | $variables['categoryGroup'] = $categoryGroup; 155 | 156 | // Get layout model 157 | $variables['layout'] = craft()->dashCols_layouts->getLayoutByCategoryGroupId($categoryGroup->id); 158 | if (!$variables['layout']) $variables['layout'] = new DashCols_LayoutModel(); 159 | 160 | $variables['crumb'] = array( 161 | 'label' => Craft::t($categoryGroup->name), 162 | 'url' => UrlHelper::getUrl('dashcols/categories/' . $categoryGroup->handle), 163 | ); 164 | 165 | // Get default + meta fields 166 | $variables['defaultFields'] = craft()->dashCols_fields->getDefaultFields('categories'); 167 | $variables['metaFields'] = craft()->dashCols_fields->getMetaFields('categories'); 168 | 169 | // Get redirect URL 170 | $variables['redirectUrl'] = UrlHelper::getUrl('categories/' . $categoryGroup->handle); 171 | 172 | return $this->renderEditLayout($variables); 173 | 174 | } 175 | 176 | /** 177 | * @access public 178 | * @return mixed 179 | */ 180 | public function actionEditAssetSourceLayout(array $variables = array()) 181 | { 182 | 183 | $this->requireCraftVersion(); 184 | 185 | if (!isset($variables['assetSourceHandleOrId'])) { 186 | $assetSources = craft()->dashCols->getAssetSources(); 187 | if (!$assetSources) throw new HttpException(404); 188 | craft()->request->redirect(UrlHelper::getUrl('dashcols/assets/' . $assetSources[0]->handle)); 189 | exit(); 190 | } 191 | 192 | // Get tab nav items (all asset sources) 193 | $allAssetSources = craft()->dashCols->getAssetSources(); 194 | $variables['tabNav'] = array(); 195 | if ($allAssetSources) 196 | { 197 | foreach ($allAssetSources as $assetSource) 198 | { 199 | $variables['tabNav'][] = array( 200 | 'name' => $assetSource->name, 201 | 'handle' => $assetSource->handle, 202 | 'url' => 'dashcols/assets/' . $assetSource->handle, 203 | ); 204 | } 205 | } 206 | 207 | // Set selected tab 208 | $variables['selectedTab'] = 'assets'; 209 | 210 | // Get asset source 211 | $assetSource = craft()->dashCols->getAssetSourceByHandleOrId($variables['assetSourceHandleOrId']); 212 | if (!$assetSource) throw new HttpException(404); 213 | $variables['assetSource'] = $assetSource; 214 | 215 | // Get layout model 216 | $variables['layout'] = craft()->dashCols_layouts->getLayoutByAssetSourceId($assetSource->id); 217 | if (!$variables['layout']) $variables['layout'] = new DashCols_LayoutModel(); 218 | 219 | $variables['crumb'] = array( 220 | 'label' => Craft::t($assetSource->name), 221 | 'url' => UrlHelper::getUrl('dashcols/assets/' . $assetSource->handle), 222 | ); 223 | 224 | // Get default + meta fields 225 | $variables['defaultFields'] = craft()->dashCols_fields->getDefaultFields('assets'); 226 | $variables['metaFields'] = craft()->dashCols_fields->getMetaFields('assets'); 227 | 228 | // Get redirect URL 229 | $variables['redirectUrl'] = UrlHelper::getUrl('assets'); 230 | 231 | return $this->renderEditLayout($variables); 232 | 233 | } 234 | 235 | /** 236 | * @access public 237 | * @return mixed 238 | */ 239 | public function actionEditUserGroupLayout(array $variables = array()) 240 | { 241 | 242 | $this->requireCraftVersion(); 243 | 244 | // Get tab nav items (all user groups) 245 | $variables['tabNav'] = array( 246 | array( 247 | 'name' => Craft::t('All users'), 248 | 'url' => 'dashcols/users', 249 | ), 250 | ); 251 | $allUserGroups = craft()->dashCols->getUserGroups(); 252 | if ($allUserGroups) 253 | { 254 | foreach ($allUserGroups as $userGroup) 255 | { 256 | $variables['tabNav'][] = array( 257 | 'name' => $userGroup->name, 258 | 'handle' => $userGroup->handle, 259 | 'url' => 'dashcols/users/' . $userGroup->handle, 260 | ); 261 | } 262 | } 263 | 264 | // Set selected tab 265 | $variables['selectedTab'] = 'users'; 266 | 267 | if (!isset($variables['userGroupHandleOrId'])) { 268 | $variables['listingHandle'] = 'users'; // All users 269 | return $this->actionEditListingLayout($variables); 270 | } 271 | 272 | // Get user group 273 | $userGroup = craft()->dashCols->getUserGroupByHandleOrId($variables['userGroupHandleOrId']); 274 | if (!$userGroup) throw new HttpException(404); 275 | $variables['userGroup'] = $userGroup; 276 | 277 | // Get layout model 278 | $variables['layout'] = craft()->dashCols_layouts->getLayoutByUserGroupId($userGroup->id); 279 | if (!$variables['layout']) $variables['layout'] = new DashCols_LayoutModel(); 280 | 281 | $variables['crumb'] = array( 282 | 'label' => Craft::t($userGroup->name), 283 | 'url' => UrlHelper::getUrl('dashcols/users/' . $userGroup->handle), 284 | ); 285 | 286 | // Get default + meta fields 287 | $variables['defaultFields'] = craft()->dashCols_fields->getDefaultFields('users'); 288 | $variables['metaFields'] = craft()->dashCols_fields->getMetaFields('users'); 289 | 290 | // Get redirect URL 291 | $variables['redirectUrl'] = UrlHelper::getUrl('users'); 292 | 293 | return $this->renderEditLayout($variables); 294 | 295 | } 296 | 297 | /** 298 | * @access public 299 | * @return mixed 300 | */ 301 | public function actionEditListingLayout(array $variables = array()) 302 | { 303 | 304 | $this->requireCraftVersion(); 305 | 306 | if (!isset($variables['listingHandle']) || !in_array($variables['listingHandle'], array('entries', 'singles', 'users'))) { 307 | throw new HttpException(404); 308 | } 309 | 310 | // Get listing attributes 311 | $listing = craft()->dashCols->getListingByHandle($variables['listingHandle']); 312 | if (!$listing) throw new HttpException(404); 313 | $listingHandle = $variables['listingHandle']; 314 | $listingEditUrl = UrlHelper::getUrl('dashcols/' . $listing->url); 315 | 316 | 317 | // Get layout model 318 | $variables['layout'] = craft()->dashCols_layouts->getLayoutByListingHandle($listingHandle); 319 | if (!$variables['layout']) $variables['layout'] = new DashCols_LayoutModel(); 320 | 321 | // Breadcrumb 322 | $variables['crumb'] = array( 323 | 'label' => $listing->name, 324 | 'url' => $listingEditUrl, 325 | ); 326 | 327 | // Get default + meta fields 328 | $variables['defaultFields'] = craft()->dashCols_fields->getDefaultFields($listingHandle); 329 | $variables['metaFields'] = craft()->dashCols_fields->getMetaFields($listingHandle); 330 | 331 | // Get redirect URL 332 | $variables['redirectUrl'] = UrlHelper::getUrl($listing->url); 333 | 334 | return $this->renderEditLayout($variables); 335 | 336 | } 337 | 338 | /** 339 | * @access protected 340 | * @return mixed 341 | */ 342 | protected function renderEditLayout(array $variables = array()) 343 | { 344 | 345 | if (craft()->dashCols->isCpSectionDisabled()) { 346 | throw new HttpException(404); 347 | } 348 | 349 | // Build breadcrumbs 350 | $variables['crumbs'] = array( 351 | array( 352 | 'label' => craft()->dashCols->getPlugin()->getName(), 353 | 'url' => UrlHelper::getUrl('dashcols'), 354 | ), 355 | array( 356 | 'label' => Craft::t('Edit layouts'), 357 | 'url' => UrlHelper::getUrl('dashcols/layouts'), 358 | ), 359 | $variables['crumb'], 360 | ); 361 | 362 | // Get tabs 363 | $variables['tabs'] = craft()->dashCols->getCpTabs(); 364 | 365 | // Render 366 | return $this->renderTemplate('dashCols/_layouts/_edit', $variables); 367 | 368 | } 369 | 370 | public function actionSaveLayout() 371 | { 372 | 373 | if (craft()->dashCols->isCpSectionDisabled()) { 374 | throw new HttpException(404); 375 | } 376 | 377 | $this->requirePostRequest(); 378 | 379 | $request = craft()->request; 380 | 381 | $layout = new DashCols_LayoutModel(); 382 | $layout->id = ($layoutId = $request->getPost('layoutId')) ? $layoutId : null; 383 | 384 | $layout->sectionId = $request->getPost('sectionId'); 385 | $layout->categoryGroupId = $request->getPost('categoryGroupId'); 386 | $layout->assetSourceId = $request->getPost('assetSourceId'); 387 | $layout->userGroupId = $request->getPost('userGroupId'); 388 | $layout->listingHandle = $request->getPost('listingHandle'); 389 | 390 | if ($layout->sectionId) { 391 | $section = craft()->dashCols->getSectionById($layout->sectionId); 392 | } else if ($layout->categoryGroupId) { 393 | $section = craft()->dashCols->getCategoryGroupById($layout->categoryGroupId); 394 | } else if ($layout->assetSourceId) { 395 | $section = craft()->dashCols->getAssetSourceById($layout->assetSourceId); 396 | } else if ($layout->userGroupId) { 397 | $section = craft()->dashCols->getUserGroupById($layout->userGroupId); 398 | } else if ($layout->listingHandle) { 399 | $section = craft()->dashCols->getListingByHandle($layout->listingHandle); 400 | } else { 401 | throw new HttpException(404); 402 | } 403 | 404 | $fieldLayout = craft()->fields->assembleLayoutFromPost(); 405 | $fieldLayout->type = ElementType::Asset; 406 | 407 | $layout->setFieldLayout($fieldLayout); 408 | 409 | // Get hidden fields 410 | $hiddenFields = array(); 411 | foreach ($request->getPost('hiddenFields') as $key => $value) { 412 | if ($value !== '1') { 413 | $hiddenFields[] = $key; 414 | } 415 | } 416 | $layout->hiddenFields = !empty($hiddenFields) ? $hiddenFields : false; 417 | 418 | // Get meta fields 419 | $metaFields = array(); 420 | foreach ($request->getPost('metaFields') as $key => $value) { 421 | if ($value === '1') { 422 | $metaFields[] = $key; 423 | } 424 | } 425 | $layout->metaFields = !empty($metaFields) ? $metaFields : false; 426 | 427 | if (craft()->dashCols_layouts->saveLayout($layout)) { 428 | craft()->userSession->setNotice(Craft::t('Layout for ' . $section->name . ' saved!')); 429 | $this->redirectToPostedUrl($layout); 430 | } else { 431 | craft()->userSession->setError(Craft::t('Something went wrong. Layout not saved.')); 432 | } 433 | 434 | craft()->urlManager->setRouteVariables(array( 435 | 'layout' => $layout, 436 | )); 437 | 438 | } 439 | 440 | private function requireCraftVersion() 441 | { 442 | if (!craft()->dashCols->isCraftRequiredVersion()) 443 | { 444 | return $this->renderTemplate('dashCols/_legacy'); 445 | } 446 | } 447 | 448 | } 449 | -------------------------------------------------------------------------------- /dashcols/migrations/m150212_090452_dashcols_change_hiddenfields_column.php: -------------------------------------------------------------------------------- 1 | alterColumn( 'dashcols_layouts', 'hiddenFields', 'string' ); 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dashcols/migrations/m150402_144345_dashcols_addMetaFieldsColumn.php: -------------------------------------------------------------------------------- 1 | addColumn( 'dashcols_layouts', 'metaFields', 'string' ); 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /dashcols/migrations/m151002_144500_dashcols_addUserGroupIdColumn.php: -------------------------------------------------------------------------------- 1 | addColumnAfter( 'dashcols_layouts', 'userGroupId', ColumnType::Int, 'categoryGroupId' ); 18 | $this->addForeignKey( 'dashcols_layouts', 'userGroupId', 'usergroups', 'id', 'CASCADE', 'CASCADE' ); 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dashcols/migrations/m151103_153210_dashcols_addAssetSourceIdColumn.php: -------------------------------------------------------------------------------- 1 | addColumnAfter( 'dashcols_layouts', 'assetSourceId', ColumnType::Int, 'categoryGroupId' ); 18 | $this->addForeignKey( 'dashcols_layouts', 'assetSourceId', 'assetsources', 'id', 'CASCADE', 'CASCADE' ); 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dashcols/models/DashCols_LayoutModel.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashCols_LayoutModel extends BaseModel 15 | { 16 | 17 | /** 18 | * @access protected 19 | * @return array 20 | */ 21 | protected function defineAttributes() 22 | { 23 | return array( 24 | 'id' => AttributeType::Number, 25 | 'sectionId' => AttributeType::Number, 26 | 'categoryGroupId' => AttributeType::Number, 27 | 'assetSourceId' => AttributeType::Number, 28 | 'userGroupId' => AttributeType::Number, 29 | 'listingHandle' => AttributeType::String, 30 | 'fieldLayoutId' => AttributeType::Number, 31 | 'customFields' => AttributeType::Mixed, 32 | 'hiddenFields' => AttributeType::Mixed, 33 | 'metaFields' => AttributeType::Mixed, 34 | ); 35 | } 36 | 37 | /** 38 | * @return array 39 | */ 40 | public function behaviors() 41 | { 42 | return array( 43 | 'fieldLayout' => new FieldLayoutBehavior( null ), 44 | ); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /dashcols/records/DashCols_LayoutRecord.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashCols_LayoutRecord extends BaseRecord 15 | { 16 | public function getTableName() 17 | { 18 | return 'dashcols_layouts'; 19 | } 20 | 21 | /** 22 | * @access protected 23 | * @return array 24 | */ 25 | protected function defineAttributes() 26 | { 27 | return array( 28 | 'sectionId' => AttributeType::Number, 29 | 'categoryGroupId' => AttributeType::Number, 30 | 'userGroupId' => AttributeType::Number, 31 | 'assetSourceId' => AttributeType::Number, 32 | 'listingHandle' => AttributeType::String, 33 | 'fieldLayoutId' => AttributeType::Number, 34 | 'hiddenFields' => AttributeType::Mixed, 35 | 'metaFields' => AttributeType::Mixed, 36 | ); 37 | } 38 | 39 | /** 40 | * @access public 41 | * @return array 42 | */ 43 | public function defineRelations() 44 | { 45 | return array( 46 | 'section' => array( 47 | static::BELONGS_TO, 48 | 'SectionRecord', 49 | 'sectionId', 50 | 'onDelete' => static::CASCADE, 51 | ), 52 | 'categoryGroup' => array( 53 | static::BELONGS_TO, 54 | 'CategoryGroupRecord', 55 | 'categoryGroupId', 56 | 'onDelete' => static::CASCADE, 57 | ), 58 | 'assetSource' => array( 59 | static::BELONGS_TO, 60 | 'AssetSourceRecord', 61 | 'assetSourceId', 62 | 'onDelete' => static::CASCADE, 63 | ), 64 | 'userGroup' => array( 65 | static::BELONGS_TO, 66 | 'UserGroupRecord', 67 | 'userGroupId', 68 | 'onDelete' => static::CASCADE, 69 | ), 70 | 'fieldLayout' => array( 71 | static::BELONGS_TO, 72 | 'FieldLayoutRecord', 73 | 'onDelete' => static::SET_NULL 74 | ), 75 | ); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /dashcols/resources/css/dashcols.cp.css: -------------------------------------------------------------------------------- 1 | .tab#tab-dashColsIndex:before { 2 | content: "home"; 3 | font-family: 'Craft'; 4 | speak: none; 5 | -webkit-font-feature-settings: "liga", "dlig"; 6 | -moz-font-feature-settings: "liga", "dlig"; 7 | font-feature-settings: "liga", "dlig"; 8 | text-rendering: optimizeLegibility; 9 | font-weight: normal; 10 | font-variant: normal; 11 | text-transform: none; 12 | -webkit-font-smoothing: anti-aliased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | user-select: none; 18 | word-wrap: normal !important; 19 | line-height: 1; 20 | position: relative; 21 | top: 1px; } 22 | 23 | .dashCols-layoutGroupTable { 24 | margin-bottom: 20px; } 25 | 26 | /* 27 | * About page 28 | * 29 | */ 30 | .dashCols-aboutPage td.desc { 31 | color: rgba(0, 0, 0, 0.5); } 32 | 33 | /* 34 | * Edit template 35 | * 36 | */ 37 | #dashCols-sectionEdit, 38 | #dashCols-defaultFields { 39 | width: 100%; 40 | float: left; 41 | clear: both; 42 | margin-bottom: 20px; } 43 | 44 | #dashCols-actions { 45 | width: 100%; 46 | float: left; 47 | clear: both; } 48 | 49 | #dashCols-sections { 50 | width: 100%; 51 | clear: both; 52 | float: left; } 53 | #dashCols-sections ul { 54 | list-style: none; 55 | margin: 0; 56 | padding: 0; } 57 | #dashCols-sections li { 58 | display: inline-block; 59 | width: auto; 60 | float: left; 61 | margin: 0 10px 10px 0; } 62 | 63 | #dashCols-sectionEdit { 64 | width: 100%; 65 | clear: both; 66 | float: left; } 67 | 68 | /* 69 | * CP section footer 70 | * 71 | */ 72 | #dashCols-colophone { 73 | width: 100%; 74 | float: left; 75 | clear: both; 76 | font-size: 11px; 77 | color: #8f98a3; 78 | text-align: right; 79 | margin-top: 20px; } 80 | -------------------------------------------------------------------------------- /dashcols/resources/css/dashcols.index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * Index table styles 4 | * 5 | */ 6 | #dashCols-editButton { 7 | margin-top: 10px; } 8 | 9 | .dashCols-scrollable { 10 | overflow-x: auto; } 11 | 12 | .elements.datatablesorthelper .move.icon, 13 | .elementindex .move.icon { 14 | top: 50% !important; 15 | margin-top: -12px !important; } 16 | 17 | /* 18 | * Fields 19 | * 20 | */ 21 | .dashCols-lightswitch { 22 | font-weight: bold; 23 | padding-left: 20px; 24 | font-size: 12px; } 25 | 26 | .dashCols-lightswitchOn:before { 27 | content: "✔"; } 28 | 29 | .dashCols-lightswitchOff:before { 30 | content: "x"; } 31 | 32 | .dashCols-color { 33 | display: block; 34 | border-radius: 2px; 35 | width: 15px; 36 | height: 15px; 37 | border: 1px solid #DDDFE0; 38 | padding: 2px; 39 | overflow: hidden; } 40 | 41 | .dashCols-color .dashCols-hex { 42 | display: block; 43 | width: 100%; 44 | height: 100%; } 45 | 46 | .dashCols-positionSelect { 47 | text-transform: capitalize; } 48 | 49 | .dashCols-assetFieldImage { 50 | width: auto; 51 | float: left; 52 | max-width: 100%; 53 | position: relative; 54 | border-radius: 2px; 55 | overflow: hidden; } 56 | 57 | .dashCols-assetFieldImage img { 58 | display: block; 59 | float: left; } 60 | 61 | .dashCols-assetFieldImage .dashCols-assetFileCount { 62 | box-sizing: border-box; 63 | width: 100%; 64 | position: absolute; 65 | left: 0; 66 | bottom: 0; 67 | background-color: #333F4D; 68 | top: auto; 69 | color: #d7d9db; 70 | padding: 2px 2px 4px 4px; } 71 | 72 | .dashCols-assetFile { 73 | width: 100%; 74 | float: left; 75 | clear: both; } 76 | 77 | .dashCols-assetFileIcon { 78 | display: inline-block; 79 | margin-right: 2px; } 80 | 81 | .dashCols-assetFilename { 82 | font-family: "Lucida Console", Monaco, monospace; 83 | font-size: 10px; 84 | position: relative; 85 | top: -6px; } 86 | 87 | .dashCols-assetFileCount { 88 | display: block; 89 | width: 100%; 90 | clear: both; 91 | font-size: 10px; 92 | color: rgba(0, 0, 0, 0.5); 93 | line-height: 1; 94 | position: relative; 95 | top: -2px; } 96 | -------------------------------------------------------------------------------- /dashcols/resources/js/dashcols.cp.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | 3 | if (!window.$) { 4 | return; 5 | } 6 | 7 | var DashCols_CpSection = {}; 8 | 9 | DashCols_CpSection.init = function () { 10 | 11 | // Init submit button 12 | var $submitBtn = $('#dashCols-actions .submit:first'); 13 | if ($submitBtn.length > 0) $submitBtn.on('click', $.proxy(onSubmitButtonClick, this)); 14 | 15 | // Init tab nav select 16 | var $tabNav = $('#dashCols-editNavSelect'); 17 | if ($tabNav.length > 0) $tabNav.on('change', $.proxy(onTabNavChange, this)); 18 | 19 | } 20 | 21 | function onSubmitButtonClick (e) { 22 | 23 | // Where are we? 24 | var path = Craft.path.replace('dashcols/layouts/', ''), 25 | segments = path.split('/'); 26 | 27 | if (segments[0] === 'dashcols') segments.shift(); 28 | 29 | if (segments.length === 0) return false; 30 | 31 | // Set cached element index to the current section or category group 32 | switch (segments[0]) { 33 | 34 | case 'entries' : 35 | 36 | var selectedSource = '*', 37 | sectionId = $('input[name="sectionId"]').val(); 38 | 39 | if (sectionId !== undefined) 40 | { 41 | selectedSource = 'section:' + sectionId; 42 | } 43 | else 44 | { 45 | selectedSource = segments.length > 1 && segments[1] === 'singles' ? 'singles' : '*'; 46 | } 47 | 48 | Craft.setLocalStorage('elementindex.Entry', { 49 | selectedSource : selectedSource 50 | }); 51 | 52 | console.log('selected source', selectedSource); 53 | 54 | break; 55 | 56 | case 'categories' : 57 | 58 | var groupId = $('input[name="categoryGroupId"]').val(); 59 | 60 | if (groupId !== undefined) 61 | { 62 | Craft.setLocalStorage('elementindex.Category', { 63 | selectedSource : 'group:' + groupId 64 | }); 65 | } 66 | else 67 | { 68 | Craft.setLocalStorage('elementindex.Category', ''); 69 | } 70 | 71 | console.log('selected source', groupId); 72 | 73 | break; 74 | 75 | case 'users' : 76 | 77 | var groupId = $('input[name="userGroupId"]').val(); 78 | 79 | if (groupId !== undefined) 80 | { 81 | Craft.setLocalStorage('elementindex.User', { 82 | selectedSource : 'group:' + groupId 83 | }); 84 | } 85 | else 86 | { 87 | Craft.setLocalStorage('elementindex.User', ''); 88 | } 89 | 90 | console.log('selected source', groupId); 91 | 92 | break; 93 | 94 | case 'assets' : 95 | 96 | var folderId = $('input[name="assetSourceId"]').val(); 97 | 98 | if (folderId !== undefined) 99 | { 100 | Craft.setLocalStorage('elementindex.Asset', { 101 | selectedSource : 'folder:' + folderId 102 | }); 103 | } 104 | else 105 | { 106 | Craft.setLocalStorage('elementindex.Asset', ''); 107 | } 108 | 109 | console.log('selected source', folderId); 110 | 111 | break; 112 | 113 | } 114 | 115 | } 116 | 117 | function onTabNavChange (e) 118 | { 119 | e.preventDefault(); 120 | window.location.href = $(e.target).val(); 121 | } 122 | 123 | $(document).ready($.proxy(DashCols_CpSection.init, DashCols_CpSection)); 124 | 125 | } (window)); -------------------------------------------------------------------------------- /dashcols/resources/js/dashcols.index.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | 3 | if (!window.$) { 4 | return; 5 | } 6 | 7 | var DashCols_Index = { 8 | $sortButton : null 9 | }; 10 | 11 | DashCols_Index.init = function () { 12 | 13 | var updateHandler = $.proxy(onUpdate, this), 14 | resizeHandler = $.proxy(onResize, this); 15 | 16 | $(document).ajaxComplete(updateHandler); 17 | $(window).on('resize', resizeHandler); 18 | 19 | } 20 | 21 | DashCols_Index.evalResponsiveTable = function () { 22 | 23 | // Get DOM elements 24 | var $tableView = $('#content .tableview:first'), 25 | $table = $('#content .tableview:first table:first'); 26 | 27 | if ($tableView.length === 0 || $table.length === 0) { 28 | return false; 29 | } 30 | 31 | if ($table.outerWidth() > $tableView.outerWidth()) { 32 | $tableView.addClass('dashCols-scrollable'); 33 | } else { 34 | $tableView.removeClass('dashCols-scrollable'); 35 | } 36 | 37 | } 38 | 39 | DashCols_Index.updateEditButton = function () { 40 | 41 | var $editButton = $('#dashCols-editButton'), 42 | hasEditButton = $editButton.length > 0; 43 | 44 | if (hasEditButton) 45 | { 46 | if (this.editUrl) $editButton.attr('href', this.editUrl); 47 | else $editButton.remove(); 48 | } 49 | else if(this.editUrl) 50 | { 51 | var editButtonHtml = 'Edit columns'; 52 | $('#content').append(editButtonHtml); 53 | } 54 | 55 | } 56 | 57 | DashCols_Index.updateSortButton = function () 58 | { 59 | 60 | if (this.$sortButton === null) { 61 | this.$sortButton = $('.sortmenubtn:first'); 62 | if (this.$sortButton.length > 0) { 63 | this.$sortButton.on('click mouseenter', $.proxy(onSortMenuButtonClick, this)); 64 | } 65 | } 66 | 67 | if (this.$sortButton.length === 0) { 68 | return false; 69 | } 70 | 71 | this.updateSortMenu(); 72 | 73 | } 74 | 75 | DashCols_Index.updateSortMenu = function () { 76 | 77 | var $sortAttributes = $('.menu ul.sort-attributes:first'); 78 | 79 | if ($sortAttributes.length === 0) { 80 | requestAnimationFrame($.proxy(this.updateSortMenu, this)); 81 | return false; 82 | } 83 | 84 | var $sortAttributesItems = $sortAttributes.find('li'), 85 | $sortAttributeItem, 86 | $indexTableColumns = $('.tableview .data th'), 87 | attribute, 88 | attributeValue, 89 | attributes = []; 90 | 91 | $indexTableColumns.each(function () { 92 | attribute = $(this).data('attribute') || false; 93 | if (attribute) { 94 | attributes.push(attribute); 95 | } 96 | }); 97 | 98 | $sortAttributesItems.show().each(function () { 99 | $sortAttributeItem = $(this); 100 | attributeValue = $sortAttributeItem.find('a:first').data('attr'); 101 | if (attributeValue !== 'structure' && $.inArray(attributeValue, attributes) === -1) { 102 | $sortAttributeItem.hide(); 103 | } 104 | }); 105 | 106 | } 107 | 108 | function onUpdate(e, status, requestData) { 109 | 110 | this.editUrl = Craft.baseCpUrl + '/dashcols/', 111 | this.entryIndex = false; 112 | 113 | if (requestData.url.indexOf('elementIndex/getElements') === -1) { 114 | return false; 115 | } 116 | 117 | // Quo vadis? 118 | var currentUrl = e ? e.target.URL : window.location.href, 119 | uri = currentUrl.replace(Craft.baseCpUrl, ''), 120 | segments = uri.split('/'); 121 | 122 | if (segments[0].length === 0) { 123 | segments.shift(); 124 | } 125 | 126 | switch (segments[0]) { 127 | 128 | case 'entries' : 129 | 130 | if (!segments[1]) { 131 | this.editUrl += 'entries'; 132 | } else if (segments[1] === 'singles') { 133 | this.editUrl += 'entries/singles'; 134 | } else { 135 | this.editUrl += 'entries/section/' + segments[1] || ''; 136 | } 137 | 138 | this.entryIndex = Craft.EntryIndex || false; 139 | 140 | break; 141 | 142 | case 'categories' : 143 | 144 | this.editUrl += 'categories/' + (segments[1] || ''); 145 | this.entryIndex = Craft.CategoryIndex || false; 146 | 147 | break; 148 | 149 | case 'users' : 150 | 151 | var source = Craft.getLocalStorage('elementindex.User').selectedSource, 152 | groupId = source.replace(/^group:/, ''); 153 | 154 | this.editUrl += segments[0] + (!isNaN(groupId) ? '/' + groupId : ''); 155 | this.entryIndex = Craft.UserIndex || false; 156 | 157 | break; 158 | 159 | case 'assets' : 160 | 161 | var source = Craft.getLocalStorage('elementindex.Asset').selectedSource, 162 | folderId = source.replace(/^folder:/, ''); 163 | 164 | this.editUrl += segments[0] + (!isNaN(folderId) ? '/' + folderId : ''); 165 | this.entryIndex = Craft.AssetIndex || false; 166 | 167 | break; 168 | 169 | } 170 | 171 | this.evalResponsiveTable(); 172 | 173 | // Return if CP Section is disabled 174 | var dashColsSettings = window._DashCols || {}; 175 | if (dashColsSettings.hasOwnProperty('cpSectionDisabled') && dashColsSettings.cpSectionDisabled) return false; 176 | 177 | this.updateEditButton(); 178 | this.updateSortButton(); 179 | 180 | } 181 | 182 | function onResize(e) { 183 | this.evalResponsiveTable(); 184 | } 185 | 186 | function onSortMenuButtonClick(e) { 187 | this.updateSortMenu(); 188 | } 189 | 190 | $(document).ready($.proxy(DashCols_Index.init, DashCols_Index)); 191 | 192 | } (window)); -------------------------------------------------------------------------------- /dashcols/services/DashColsService.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashColsService extends BaseApplicationComponent 15 | { 16 | 17 | private $_plugin = null, 18 | $_sections = null, 19 | $_channels = null, 20 | $_structures = null, 21 | $_categoryGroups = null, 22 | $_userGroups = null, 23 | $_assetSources = null; 24 | 25 | /* 26 | * Returns the DashCols plugin for use in variables and the like 27 | * 28 | */ 29 | public function getPlugin() 30 | { 31 | if ($this->_plugin === null) { 32 | $this->_plugin = craft()->plugins->getPlugin('dashCols'); 33 | } 34 | return $this->_plugin; 35 | } 36 | 37 | /* 38 | * Return CP section tabs 39 | * 40 | */ 41 | public function getCpTabs() 42 | { 43 | if (!$this->isCraftRequiredVersion()) return array( 44 | 'dashColsIndex' => array( 45 | 'label' => '', 46 | 'url' => UrlHelper::getUrl('dashcols'), 47 | ), 48 | ); 49 | $tabs = array( 50 | 'dashColsIndex' => array( 51 | 'label' => '', 52 | 'url' => UrlHelper::getUrl('dashcols'), 53 | ), 54 | 'entries' => array( 55 | 'label' => Craft::t('Entries'), 56 | 'url' => UrlHelper::getUrl('dashcols/entries'), 57 | ), 58 | 'categories' => array( 59 | 'label' => Craft::t('Categories'), 60 | 'url' => UrlHelper::getUrl('dashcols/categories'), 61 | ), 62 | 'assets' => array( 63 | 'label' => Craft::t('Assets'), 64 | 'url' => UrlHelper::getUrl('dashcols/assets'), 65 | ), 66 | 'users' => array( 67 | 'label' => Craft::t('Users'), 68 | 'url' => UrlHelper::getUrl('dashcols/users'), 69 | ), 70 | ); 71 | if (!$this->getCategoryGroups()) unset($tabs['categories']); 72 | if (!$this->getAssetSources()) unset($tabs['assets']); 73 | return $tabs; 74 | } 75 | 76 | public function isCpSectionDisabled() 77 | { 78 | $settings = $this->getPlugin()->getSettings(); 79 | return isset($settings['cpSectionDisabled']) && $settings['cpSectionDisabled']; 80 | } 81 | 82 | public function isCraftRequiredVersion() 83 | { 84 | return $this->getPlugin()->isCraftRequiredVersion(); 85 | } 86 | 87 | /* 88 | * Entries 89 | * 90 | */ 91 | public function getSections() 92 | { 93 | if ($this->_sections === null) { 94 | $this->_sections = craft()->sections->allSections; 95 | } 96 | return $this->_sections; 97 | } 98 | 99 | public function getChannels() 100 | { 101 | if ($this->_channels === null) { 102 | $sections = $this->getSections(); 103 | $channels = array(); 104 | foreach ($sections as $section) { 105 | if ($section->type == 'channel') { 106 | $channels[] = $section; 107 | } 108 | } 109 | $this->_channels = $channels; 110 | } 111 | 112 | return $this->_channels; 113 | } 114 | 115 | public function getStructures() 116 | { 117 | if ($this->_structures === null) { 118 | $sections = $this->getSections(); 119 | $structures = array(); 120 | foreach ($sections as $section) { 121 | if ($section->type == 'structure') { 122 | $structures[] = $section; 123 | } 124 | } 125 | $this->_structures = $structures; 126 | } 127 | 128 | return $this->_structures; 129 | } 130 | 131 | public function getSectionByHandleOrId($sectionHandleOrId) 132 | { 133 | return ctype_digit($sectionHandleOrId) ? $this->getSectionById($sectionHandleOrId) : $this->getSectionByHandle($sectionHandleOrId); 134 | } 135 | 136 | public function getSectionById($sectionId) 137 | { 138 | foreach ($this->getSections() as $section) { 139 | if ($section->id == $sectionId) { 140 | return $section; 141 | } 142 | } 143 | return false; 144 | } 145 | 146 | public function getSectionByHandle($sectionHandle) 147 | { 148 | foreach ($this->getSections() as $section) { 149 | if ($section->handle == $sectionHandle) { 150 | return $section; 151 | } 152 | } 153 | return false; 154 | } 155 | 156 | /* 157 | * Category groups 158 | * 159 | */ 160 | public function getCategoryGroups() 161 | { 162 | if ($this->_categoryGroups === null) { 163 | $this->_categoryGroups = craft()->categories->allGroups; 164 | } 165 | return $this->_categoryGroups; 166 | } 167 | 168 | public function getCategoryGroupByHandleOrId($categoryGroupHandleOrId) 169 | { 170 | return ctype_digit($categoryGroupHandleOrId) ? $this->getCategoryGroupById($categoryGroupHandleOrId) : $this->getCategoryGroupByHandle($categoryGroupHandleOrId); 171 | } 172 | 173 | public function getCategoryGroupById($categoryGroupId) 174 | { 175 | foreach ($this->getCategoryGroups() as $categoryGroup) { 176 | if ($categoryGroup->id == $categoryGroupId) { 177 | return $categoryGroup; 178 | } 179 | } 180 | return false; 181 | } 182 | 183 | public function getCategoryGroupByHandle($categoryGroupHandle) 184 | { 185 | foreach ($this->getCategoryGroups() as $categoryGroup) { 186 | if ($categoryGroup->handle == $categoryGroupHandle) { 187 | return $categoryGroup; 188 | } 189 | } 190 | return false; 191 | } 192 | 193 | /* 194 | * User groups 195 | * 196 | */ 197 | public function getUserGroups() 198 | { 199 | if ($this->_userGroups === null) { 200 | $this->_userGroups = craft()->userGroups->allGroups; 201 | } 202 | return $this->_userGroups; 203 | } 204 | 205 | public function getUserGroupByHandleOrId($userGroupHandleOrId) 206 | { 207 | return ctype_digit($userGroupHandleOrId) ? $this->getUserGroupById($userGroupHandleOrId) : $this->getUserGroupByHandle($userGroupHandleOrId); 208 | } 209 | 210 | public function getUserGroupById($userGroupId) 211 | { 212 | foreach ($this->getUserGroups() as $userGroup) 213 | { 214 | if ($userGroup->id === $userGroupId) return $userGroup; 215 | } 216 | return false; 217 | } 218 | 219 | public function getUserGroupByHandle($userGroupHandle) 220 | { 221 | foreach ($this->getUserGroups() as $userGroup) 222 | { 223 | if ($userGroup->handle === $userGroupHandle) return $userGroup; 224 | } 225 | return false; 226 | } 227 | 228 | /* 229 | * Asset sources 230 | * 231 | */ 232 | public function getAssetSources() 233 | { 234 | if ($this->_assetSources === null) { 235 | $this->_assetSources = craft()->assetSources->allSources; 236 | } 237 | return $this->_assetSources; 238 | } 239 | 240 | public function getAssetSourceByHandleOrId($assetSourceHandleOrId) 241 | { 242 | return ctype_digit($assetSourceHandleOrId) ? $this->getAssetSourceById($assetSourceHandleOrId) : $this->getAssetSourceByHandle($assetSourceHandleOrId); 243 | } 244 | 245 | public function getAssetSourceById($assetSourceId) 246 | { 247 | foreach ($this->getAssetSources() as $assetSource) 248 | { 249 | if ($assetSource->id === $assetSourceId) return $assetSource; 250 | } 251 | return false; 252 | } 253 | 254 | public function getAssetSourceByHandle($assetSourceHandle) 255 | { 256 | foreach ($this->getAssetSources() as $assetSource) 257 | { 258 | if ($assetSource->handle === $assetSourceHandle) return $assetSource; 259 | } 260 | return false; 261 | } 262 | 263 | /* 264 | * Listings 265 | * 266 | */ 267 | public function getListingByHandle($listingHandle) 268 | { 269 | $listing = (object) array(); 270 | switch ($listingHandle) { 271 | case '*' : case 'entries' : 272 | $listing->name = Craft::t('All entries'); 273 | $listing->url = 'entries'; 274 | break; 275 | case 'singles' : 276 | $listing->name = Craft::t('Singles'); 277 | $listing->url = 'entries/singles'; 278 | break; 279 | case 'users' : 280 | $listing->name = Craft::t('All users'); 281 | $listing->url = 'users'; 282 | break; 283 | default : 284 | return false; 285 | } 286 | return $listing; 287 | } 288 | 289 | } -------------------------------------------------------------------------------- /dashcols/services/DashCols_AttributesService.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashCols_AttributesService extends BaseApplicationComponent 15 | { 16 | 17 | private $_element, 18 | $_attribute, 19 | $_attributeHandle, 20 | $_attributeField, 21 | $_attributeClass; 22 | 23 | public function modifyIndexTableAttributes(&$attributes) 24 | { 25 | 26 | // Get layout 27 | if (!$dashColsLayout = craft()->dashCols_layouts->getLayout()) { 28 | return false; 29 | } 30 | 31 | // Add meta fields 32 | if (($metaFields = $dashColsLayout->metaFields) && is_array($metaFields)) { 33 | $allMetaFields = craft()->dashCols_fields->getMetaFields(); 34 | foreach ($metaFields as $attribute) { 35 | if (isset($allMetaFields[$attribute])) { 36 | $attributes[$attribute] = $allMetaFields[$attribute]; 37 | } 38 | } 39 | 40 | } 41 | 42 | // Add custom fields 43 | if ($customFields = $dashColsLayout->customFields) { 44 | foreach ($customFields as $customFieldHandle => $customField) { 45 | $attributes[$customFieldHandle] = $customField; 46 | } 47 | 48 | } 49 | 50 | // Remove hidden fields 51 | if ($dashColsLayout->hiddenFields && is_array($dashColsLayout->hiddenFields)) { 52 | foreach ($dashColsLayout->hiddenFields as $attribute) { 53 | if (isset($attributes[$attribute])) { 54 | unset($attributes[$attribute]); 55 | } 56 | } 57 | } 58 | 59 | } 60 | 61 | public function modifyIndexSortableAttributes(&$attributes) 62 | { 63 | $sortableFields = craft()->dashCols_fields->getSortableFields(); 64 | $attributes += $sortableFields; 65 | } 66 | 67 | /** 68 | * @access public 69 | * @return mixed 70 | */ 71 | public function getAttributeHtml($element, $attribute) 72 | { 73 | 74 | // Don't do anything for default attributes 75 | $defaultFields = craft()->dashCols_fields->getDefaultFields(); 76 | if (in_array($attribute, array_keys($defaultFields))) { 77 | return null; 78 | } 79 | 80 | // A little hack to retrieve the full author from the author ID 81 | if ($attribute === 'authorId') { 82 | $attribute = 'author'; 83 | } else if ($attribute === 'typeId') { 84 | $attribute = 'type'; 85 | } 86 | 87 | // Cache data about the attribute's field 88 | //$customFields = craft()->dashCols_fields->getCustomFields(); 89 | $this->_attributeHandle = $attribute; 90 | $this->_attributeField = craft()->dashCols_fields->getCustomFieldByHandle($this->_attributeHandle); 91 | $this->_attributeClass = $this->_attributeField ? $this->_attributeField->fieldType->classHandle : ''; 92 | 93 | // Handle null values 94 | if (!$elementAttribute = @$element->$attribute) { 95 | switch ($this->_attributeClass) 96 | { 97 | case 'Number' : 98 | // Zeros are returned as null, so we'll need to hack in a zero :) Thanks Lindsey! 99 | $elementAttribute = '0'; 100 | break; 101 | default : 102 | return false; 103 | } 104 | } 105 | 106 | // Cache the element and attribute value 107 | $this->_element = $element; 108 | $this->_attribute = $elementAttribute; 109 | 110 | // Return html from string or object value 111 | $attributeValue = is_object($elementAttribute) ? $this->_getObjectAttributeHtml() : $this->_getStringValueTableAttributeHtml(); 112 | 113 | // Set attribute classes 114 | $attributeCssClasses = array('dashCols-attribute'); 115 | if ($this->_attributeField) { 116 | $attributeCssClasses[] = 'dashCols-' . lcfirst($this->_attributeField->fieldType->classHandle); 117 | } 118 | 119 | return '' . $attributeValue . ''; 120 | 121 | } 122 | 123 | /** 124 | * @access private 125 | * @return string 126 | * 127 | * Method returns attribute HTML for string values 128 | * 129 | */ 130 | private function _getStringValueTableAttributeHtml() { 131 | 132 | switch ($this->_attributeClass) { 133 | 134 | case 'Lightswitch' : 135 | 136 | $attributeHtml = $this->_getLightswitchTableAttributeHtml(); 137 | 138 | break; 139 | 140 | case 'Color' : 141 | 142 | $attributeHtml = $this->_getColorTableAttributeHtml(); 143 | 144 | break; 145 | 146 | default : 147 | 148 | // Could be a URL! 149 | if (filter_var($this->_attribute, FILTER_VALIDATE_URL)) { 150 | 151 | // ...but is it an external URL? 152 | $siteUrl = craft()->urlHelper->getSiteUrl(); 153 | $url = $this->_attribute; 154 | $urlComponents = parse_url($url); 155 | $isExternal = !empty($urlComponents['host']) && strcasecmp($urlComponents['host'], $_SERVER['HTTP_HOST']); 156 | 157 | if (!$isExternal) { 158 | $attributeHtml = '' . $url . ''; 159 | } else { 160 | $attributeHtml = '' . $url . ''; 161 | } 162 | 163 | } else { 164 | 165 | // Ye olde generic string, I guess. 166 | return $this->_getYeOldeGenericStringValue($this->_attribute); 167 | 168 | } 169 | 170 | break; 171 | 172 | } 173 | 174 | return $attributeHtml; 175 | 176 | } 177 | 178 | /** 179 | * @access private 180 | * @return string 181 | * 182 | * Method returns attribute HTML for object values 183 | * 184 | */ 185 | private function _getObjectAttributeHtml() 186 | { 187 | 188 | if ($class = @get_class($this->_attribute)) { 189 | 190 | switch ($class) { 191 | 192 | case 'Craft\ElementCriteriaModel' : 193 | 194 | return $this->_getElementCriteriaTableAttributeHtml(); 195 | 196 | case 'Craft\DateTime' : 197 | 198 | return $this->_getDateTimeTableAttributeHtml(); 199 | 200 | case 'Craft\MultiOptionsFieldData' : 201 | case 'Craft\SingleOptionFieldData' : 202 | 203 | return $this->_getOptionsFieldDataTableAttributeHtml(); 204 | 205 | case 'Craft\UserModel' : 206 | 207 | return $this->_getUserTableAttributeHtml(); 208 | 209 | case 'Craft\EntryTypeModel' : 210 | 211 | return $this->_getEntryTypeTableAttributeHtml(); 212 | 213 | case 'Craft\SmartMap_AddressModel' : 214 | 215 | return (string) $this->_attribute; 216 | 217 | case 'Craft\DoxterModel' : 218 | 219 | $attributeValue = (string) $this->_attribute; 220 | if (isset(craft()->doxter)) { 221 | $attributeValue = craft()->doxter->parse($attributeValue); 222 | } 223 | return $this->_getYeOldeGenericStringValue($attributeValue); 224 | 225 | default : 226 | 227 | $contentAttribute = $this->_attributeField->fieldType->defineContentAttribute(); 228 | $contentColumn = $contentAttribute && isset($contentAttribute['column']) ? $contentAttribute['column'] : false; 229 | 230 | if ($contentColumn === 'text' || $contentColumn === 'longtext') { 231 | $attributeValue = (string) $this->_attribute; 232 | return $this->_getYeOldeGenericStringValue($attributeValue); 233 | } 234 | 235 | DashColsPlugin::log('Unknown class: ' . $class); 236 | 237 | } 238 | 239 | } 240 | 241 | return false; 242 | 243 | } 244 | 245 | /** 246 | * @access private 247 | * @return string 248 | * 249 | * Method returns attribute HTML for ElementCriteriaModel instances 250 | * 251 | */ 252 | private function _getElementCriteriaTableAttributeHtml() 253 | { 254 | 255 | // Element types 256 | $classHandle = $this->_attribute->elementType->classHandle; 257 | 258 | switch ($classHandle) { 259 | 260 | case 'Asset' : 261 | 262 | return $this->_getAssetTableAttributeHtml(); 263 | 264 | break; 265 | 266 | case 'User' : 267 | 268 | return $this->_getUserTableAttributeHtml(); 269 | 270 | break; 271 | 272 | case 'Tag' : 273 | 274 | return $this->_getTagTableAttributeHtml(); 275 | 276 | break; 277 | 278 | case 'Category' : 279 | case 'Entry' : 280 | 281 | return $this->_getEntryTableAttributeHtml(); 282 | 283 | break; 284 | 285 | default : 286 | 287 | DashColsPlugin::log('Unknown element: ' . $classHandle); 288 | 289 | } 290 | 291 | } 292 | 293 | /** 294 | * @access private 295 | * @return string 296 | * 297 | * Method returns attribute HTML for Assets 298 | * 299 | */ 300 | private function _getAssetTableAttributeHtml() 301 | { 302 | 303 | if (!$asset = $this->_attribute[0]) { 304 | return false; 305 | } 306 | 307 | $totalCount = count($this->_attribute); 308 | 309 | // I can haz SVG transforms? 310 | $svgTransformSupport = version_compare(craft()->getVersion(), '2.4', '>=') && craft()->images->isImagick(); 311 | 312 | if ($asset->kind === 'image' && (strtolower($asset->extension) !== 'svg' || $svgTransformSupport)) { 313 | 314 | // Image 315 | $assetWidth = 60; 316 | $assetHeight = 60; 317 | 318 | $attributeHtmlClass = 'image'; 319 | 320 | $attributeHtml = '' . $asset->title . ''; 321 | 322 | if ($totalCount > 1) { 323 | $attributeHtml .= '
' . $totalCount . ' ' . Craft::t('files') . '
'; 324 | } 325 | 326 | } else { 327 | 328 | // File 329 | $iconSize = 20; 330 | 331 | $attributeHtmlClass = 'file'; 332 | 333 | $attributeHtml = '
 '.$asset->filename . '
'; 334 | 335 | if ($totalCount > 1) { 336 | $attributeHtml .= '
+ ' . ($totalCount - 1) . ' ' . Craft::t('more') . '
'; 337 | } 338 | 339 | } 340 | 341 | return '
' . $attributeHtml . '
'; 342 | 343 | } 344 | 345 | /** 346 | * @access private 347 | * @return string 348 | * 349 | * Method returns attribute HTML for Entries and Categories 350 | * 351 | */ 352 | private function _getEntryTableAttributeHtml() 353 | { 354 | 355 | $elements = $this->_attribute->find(); 356 | $temp = array(); 357 | 358 | foreach ($elements as $element) { 359 | 360 | $attribute = $element->title; 361 | 362 | if ($element->cpEditUrl) { 363 | $attribute = '' . $attribute . ''; 364 | } 365 | 366 | $temp[] = $attribute; 367 | 368 | } 369 | 370 | return !empty($temp) ? implode(', ', $temp) : false; 371 | 372 | } 373 | 374 | /** 375 | * @access private 376 | * @return string 377 | * 378 | * Method returns attribute HTML for Users 379 | * 380 | */ 381 | private function _getUserTableAttributeHtml() 382 | { 383 | 384 | if (!$this->_attribute->id) { 385 | $elements = $this->_attribute->find(); 386 | } else { 387 | $elements = array($this->_attribute); 388 | } 389 | 390 | $temp = array(); 391 | 392 | foreach ($elements as $element) { 393 | 394 | $name = ''; 395 | 396 | if ($firstName = $element->firstName) { 397 | $name = $firstName . ' '; 398 | } 399 | 400 | if ($lastName = $element->lastName) { 401 | $name .= $lastName; 402 | } 403 | 404 | $attribute = $name !== '' ? trim($name) : $element->name; 405 | 406 | if ($element->cpEditUrl) { 407 | $attribute = '' . $attribute . ''; 408 | } 409 | 410 | $temp[] = $attribute; 411 | 412 | } 413 | 414 | return !empty($temp) ? implode(', ', $temp) : false; 415 | 416 | } 417 | 418 | /** 419 | * @access private 420 | * @return string 421 | * 422 | * Method returns attribute HTML for Users 423 | * 424 | */ 425 | private function _getEntryTypeTableAttributeHtml() 426 | { 427 | return $this->_attribute->name ?: ''; 428 | } 429 | 430 | /** 431 | * @access private 432 | * @return string 433 | * 434 | * Method returns attribute HTML for Tags 435 | * 436 | */ 437 | private function _getTagTableAttributeHtml() 438 | { 439 | 440 | $elements = $this->_attribute->find(); 441 | $temp = array(); 442 | 443 | foreach ($elements as $element) { 444 | 445 | $temp[] = $element->title; 446 | 447 | } 448 | 449 | return !empty($temp) ? implode(', ', $temp) : false; 450 | 451 | } 452 | 453 | /** 454 | * @access private 455 | * @return string 456 | * 457 | * Method returns attribute HTML for MultiOptionsFieldData and SingleOptionFieldData 458 | * 459 | */ 460 | private function _getOptionsFieldDataTableAttributeHtml() 461 | { 462 | 463 | $options = $this->_attribute->getOptions(); 464 | $temp = array(); 465 | 466 | foreach ($options as $option) { 467 | if ($option->selected) { 468 | $temp[] = $option->label; 469 | } 470 | } 471 | 472 | return !empty($temp) ? implode(', ', $temp) : false; 473 | 474 | } 475 | 476 | /** 477 | * @access private 478 | * @return string 479 | * 480 | * Method returns attribute HTML for Date/Time 481 | * 482 | */ 483 | private function _getDateTimeTableAttributeHtml() 484 | { 485 | 486 | if ($this->_attributeField) { 487 | 488 | $settings = $this->_attributeField->settings; 489 | 490 | if ($settings['showDate']) { 491 | $date[] = $this->_attribute->localeDate(); 492 | } 493 | 494 | if ($settings['showTime']) { 495 | $date[] = $this->_attribute->localeTime(); 496 | } 497 | 498 | return implode(' ', $date); 499 | 500 | } 501 | 502 | return $this->_attribute->localeDate() . ' ' . $this->_attribute->localeTime(); 503 | 504 | } 505 | 506 | /** 507 | * @access private 508 | * @return string 509 | * 510 | * Method returns attribute HTML for Color 511 | * 512 | */ 513 | private function _getColorTableAttributeHtml() 514 | { 515 | 516 | return ''; 517 | 518 | } 519 | 520 | /** 521 | * @access private 522 | * @return string 523 | * 524 | * Method returns attribute HTML for Lightswitch 525 | * 526 | */ 527 | private function _getLightswitchTableAttributeHtml() 528 | { 529 | return $this->_attribute === '1' ? '' : ''; 530 | } 531 | 532 | private function _getYeOldeGenericStringValue($attribute) { 533 | $attributeHtml = trim(strip_tags($attribute)); 534 | if (mb_strlen($attributeHtml) > 47) { 535 | $attributeHtml = mb_substr($attributeHtml, 0, 47) . '...'; 536 | } 537 | return $attributeHtml; 538 | } 539 | 540 | } 541 | -------------------------------------------------------------------------------- /dashcols/services/DashCols_FieldsService.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashCols_FieldsService extends BaseApplicationComponent 15 | { 16 | 17 | // Cache all custom fields here 18 | protected $_customFields = null; 19 | 20 | /* 21 | * Get FieldTypes that aren't supported by DashCols 22 | * 23 | */ 24 | public function getUnsupportedFieldTypes() 25 | { 26 | return array('Rich Text', 'Table', 'Matrix'); 27 | } 28 | 29 | /* 30 | * Return map of default fields in Craft 31 | * 32 | */ 33 | public function getDefaultFields($target = false) 34 | { 35 | 36 | switch ($target) { 37 | 38 | case 'singles' : 39 | case 'categories' : 40 | 41 | return array( 42 | 'uri' => Craft::t('URI'), 43 | ); 44 | 45 | break; 46 | 47 | case 'entries' : 48 | 49 | return array( 50 | 'uri' => Craft::t('URI'), 51 | 'postDate' => Craft::t('Post Date'), 52 | 'expiryDate' => Craft::t('Expiry Date'), 53 | ); 54 | 55 | break; 56 | 57 | case 'users' : 58 | 59 | return array( 60 | 'firstName' => Craft::t('First Name'), 61 | 'lastName' => Craft::t('Last Name'), 62 | 'email' => Craft::t('Email'), 63 | 'dateCreated' => Craft::t('Join Date'), 64 | 'lastLoginDate' => Craft::t('Last Login'), 65 | ); 66 | 67 | break; 68 | 69 | case 'assets' : 70 | 71 | return array( 72 | 'filename' => Craft::t('Filename'), 73 | 'size' => Craft::t('Size'), 74 | 'dateModified' => Craft::t('Date Modified'), 75 | ); 76 | 77 | break; 78 | 79 | default : 80 | 81 | return array( 82 | 'uri' => Craft::t('URI'), 83 | 'postDate' => Craft::t('Post Date'), 84 | 'expiryDate' => Craft::t('Expiry Date'), 85 | 'section' => Craft::t('Section'), 86 | ); 87 | 88 | } 89 | 90 | } 91 | 92 | /* 93 | * Return map of metadata fields in Craft 94 | * 95 | */ 96 | public function getMetaFields($target = false) 97 | { 98 | 99 | switch ($target) { 100 | 101 | case 'categories' : 102 | 103 | return array( 104 | 'id' => Craft::t('ID'), 105 | 'dateUpdated' => Craft::t('Updated Date'), 106 | ); 107 | 108 | break; 109 | 110 | case 'users' : 111 | 112 | return array( 113 | 'id' => Craft::t('ID'), 114 | 'preferredLocale' => Craft::t('Preferred Locale'), 115 | 'weekStartDay' => Craft::t('Week Start Day'), 116 | ); 117 | 118 | break; 119 | 120 | case 'assets' : 121 | 122 | return array( 123 | 'id' => Craft::t('ID'), 124 | 'kind' => Craft::t('Kind'), 125 | 'width' => Craft::t('Width'), 126 | 'height' => Craft::t('Height'), 127 | 'dateCreated' => Craft::t('Created Date'), 128 | 'dateUpdated' => Craft::t('Updated Date'), 129 | ); 130 | 131 | break; 132 | 133 | case 'entries' : 134 | 135 | return array( 136 | 'id' => Craft::t('ID'), 137 | 'dateUpdated' => Craft::t('Updated Date'), 138 | 'authorId' => Craft::t('Author'), 139 | 'typeId' => Craft::t('Entry Type'), 140 | ); 141 | 142 | break; 143 | 144 | case 'singles' : 145 | 146 | return array( 147 | 'id' => Craft::t('ID'), 148 | 'dateUpdated' => Craft::t('Updated Date'), 149 | 'authorId' => Craft::t('Author'), 150 | ); 151 | 152 | break; 153 | 154 | default : 155 | 156 | return array( 157 | 'id' => Craft::t('ID'), 158 | 'dateUpdated' => Craft::t('Updated Date'), 159 | 'authorId' => Craft::t('Author'), 160 | 'typeId' => Craft::t('Entry Type'), 161 | 'kind' => Craft::t('Kind'), 162 | 'width' => Craft::t('Width'), 163 | 'height' => Craft::t('Height'), 164 | 'dateCreated' => Craft::t('Created Date'), 165 | 'dateUpdated' => Craft::t('Updated Date'), 166 | 'preferredLocale' => Craft::t('Preferred Locale'), 167 | 'weekStartDay' => Craft::t('Week Start Day'), 168 | ); 169 | 170 | } 171 | 172 | } 173 | 174 | /* 175 | * Get all custom fields 176 | * 177 | */ 178 | public function getCustomFields() 179 | { 180 | return $this->_customFields ?: array(); 181 | } 182 | 183 | /* 184 | * Add custom fields to the cache 185 | * 186 | */ 187 | public function addCustomFields($fields) 188 | { 189 | 190 | $customFields = $this->getCustomFields(); 191 | 192 | foreach ($fields as $field) { 193 | 194 | if (! isset($customFields[$field->handle])) { 195 | $customFields[$field->handle] = $field; 196 | } 197 | 198 | } 199 | 200 | $this->_customFields = $customFields; 201 | 202 | } 203 | 204 | /* 205 | * Get custom field by handle 206 | * 207 | */ 208 | public function getCustomFieldByHandle($handle) 209 | { 210 | return isset($this->_customFields[$handle]) ? $this->_customFields[$handle] : false; 211 | } 212 | 213 | /* 214 | * Add a FieldLayoutModel's fields to the cache 215 | * 216 | */ 217 | public function getCustomFieldsFromFieldLayout(FieldLayoutModel $fieldLayout) 218 | { 219 | 220 | $fields = array(); 221 | $fieldLayoutFieldModels = $fieldLayout->getFields(); 222 | 223 | foreach ($fieldLayoutFieldModels as $fieldLayoutFieldModel) { 224 | if (! in_array($fieldLayoutFieldModel->field->getFieldType()->name, $this->getUnsupportedFieldTypes())) { 225 | $fields[$fieldLayoutFieldModel->field->handle] = $fieldLayoutFieldModel->field; 226 | } 227 | } 228 | 229 | return $fields; 230 | 231 | } 232 | 233 | /* 234 | * Get all sortable fields 235 | * 236 | */ 237 | public function getSortableFields() 238 | { 239 | 240 | $sortableAttributeTypes = array( 241 | AttributeType::Number, 242 | AttributeType::DateTime, 243 | AttributeType::String, 244 | AttributeType::Bool, 245 | ); 246 | 247 | $sortableFields = $this->getMetaFields(); 248 | $customFields = $this->getCustomFields(); 249 | 250 | foreach ($customFields as $handle => $field) { 251 | 252 | $fieldTypeContentAttribute = $field->fieldType->defineContentAttribute(); 253 | 254 | if (is_array($fieldTypeContentAttribute)) { 255 | $fieldTypeContentAttribute = array_shift($fieldTypeContentAttribute); 256 | } 257 | 258 | if (in_array($fieldTypeContentAttribute, $sortableAttributeTypes)) { 259 | $sortableFields[$handle] = $field; 260 | } 261 | 262 | } 263 | 264 | return $sortableFields; 265 | } 266 | 267 | } -------------------------------------------------------------------------------- /dashcols/services/DashCols_LayoutsService.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashCols_LayoutsService extends BaseApplicationComponent 15 | { 16 | 17 | protected $_layouts = null, 18 | $_currentLayout = null; 19 | 20 | public function init() 21 | { 22 | 23 | $layouts = $this->getLayouts(); 24 | 25 | // Cache the layout's fields to the FieldsService 26 | foreach ($layouts as $layout) { 27 | craft()->dashCols_fields->addCustomFields($layout->customFields); 28 | } 29 | 30 | } 31 | 32 | public function getLayouts() 33 | { 34 | if ($this->_layouts === null) { 35 | 36 | $layouts = array(); 37 | $records = DashCols_LayoutRecord::model()->findAll(); 38 | 39 | foreach ($records as $record) { 40 | 41 | $layout = DashCols_LayoutModel::populateModel($record); 42 | 43 | // Get the layouts' fields 44 | $layout->customFields = craft()->dashCols_fields->getCustomFieldsFromFieldLayout($layout->getFieldLayout()); 45 | 46 | $layouts[] = $layout; 47 | 48 | } 49 | 50 | $this->_layouts = $layouts; 51 | 52 | } 53 | 54 | return $this->_layouts; 55 | 56 | } 57 | 58 | /* 59 | * Returns the current layout 60 | * 61 | */ 62 | public function getLayout() 63 | { 64 | return $this->_currentLayout ?: false; 65 | } 66 | 67 | /* 68 | * Sets current layout 69 | * 70 | */ 71 | public function setLayout($dashColsLayout) 72 | { 73 | $this->_currentLayout = $dashColsLayout; 74 | } 75 | 76 | /* 77 | * Sets current layout from entry source 78 | * 79 | */ 80 | public function setLayoutFromEntrySource($source) 81 | { 82 | $layout = $this->getLayoutFromEntrySource($source); 83 | $this->setLayout($layout ?: false); 84 | } 85 | 86 | /* 87 | * Sets current layout from category source 88 | * 89 | */ 90 | public function setLayoutFromCategorySource($source) 91 | { 92 | $layout = $this->getLayoutFromCategorySource($source); 93 | $this->setLayout($layout ?: false); 94 | } 95 | 96 | /* 97 | * Sets current layout from asset source 98 | * 99 | */ 100 | public function setLayoutFromAssetSource($source) 101 | { 102 | $layout = $this->getLayoutFromAssetSource($source); 103 | $this->setLayout($layout ?: false); 104 | } 105 | 106 | /* 107 | * Sets current layout from user source 108 | * 109 | */ 110 | public function setLayoutFromUserSource($source) 111 | { 112 | $layout = $this->getLayoutFromUserSource($source); 113 | $this->setLayout($layout ?: false); 114 | } 115 | 116 | /* 117 | * Returns layout from entry source 118 | * 119 | */ 120 | public function getLayoutFromEntrySource($source) 121 | { 122 | 123 | $layout = false; 124 | 125 | switch ($source) { 126 | 127 | case '*' : case 'entries' : case 'singles' : 128 | 129 | // Listing 130 | if ($source === '*') { 131 | $source = 'entries'; 132 | } 133 | 134 | $layout = $this->getLayoutByListingHandle($source); 135 | 136 | break; 137 | 138 | default : 139 | 140 | if (strpos($source, ':') === false) { 141 | 142 | // Let's hope this is a valid handle 143 | if (!$section = craft()->sections->getSectionByHandle($source)) { 144 | return false; 145 | } 146 | 147 | $sectionId = $section->id; 148 | 149 | } else { 150 | 151 | // Section ID 152 | $temp = explode(':', $source); 153 | 154 | if ($temp[0] != 'section' || !isset($temp[1]) || !is_numeric($temp[1])) { 155 | return false; 156 | } 157 | 158 | $sectionId = $temp[1]; 159 | 160 | } 161 | 162 | $layout = $this->getLayoutBySectionId($sectionId); 163 | 164 | } 165 | 166 | return $layout; 167 | 168 | } 169 | 170 | /* 171 | * Returns layout from category source 172 | * 173 | */ 174 | public function getLayoutFromCategorySource($source) 175 | { 176 | 177 | if (strpos($source, ':') === false) { 178 | 179 | // Let's hope this is a valid handle 180 | if (!$categoryGroup = craft()->categories->getCategoryGroupByHandle($source)) { 181 | return false; 182 | } 183 | 184 | $categoryGroupId = $categoryGroup->id; 185 | 186 | } else { 187 | 188 | $temp = explode(':', $source); 189 | 190 | if ($temp[0] != 'group' || !isset($temp[1]) || !is_numeric($temp[1])) { 191 | return false; 192 | } 193 | 194 | $categoryGroupId = $temp[1]; 195 | 196 | } 197 | 198 | $layout = $this->getLayoutByCategoryGroupId($categoryGroupId); 199 | 200 | return $layout; 201 | 202 | } 203 | 204 | /* 205 | * Returns layout from asset source 206 | * 207 | */ 208 | public function getLayoutFromAssetSource($source) 209 | { 210 | 211 | $temp = explode(':', $source); 212 | 213 | if ($temp[0] != 'folder' || !isset($temp[1]) || !is_numeric($temp[1])) { 214 | return false; 215 | } 216 | 217 | $assetSourceId = $temp[1]; 218 | 219 | $layout = $this->getLayoutByAssetSourceId($assetSourceId); 220 | 221 | return $layout; 222 | 223 | } 224 | 225 | /* 226 | * Returns layout from user source 227 | * 228 | */ 229 | public function getLayoutFromUserSource($source) 230 | { 231 | 232 | $layout = false; 233 | 234 | switch ($source) { 235 | 236 | case '*' : 237 | 238 | $layout = $this->getLayoutByListingHandle('users'); 239 | 240 | break; 241 | 242 | default : 243 | 244 | if (strpos($source, ':') === false) { 245 | 246 | // Let's hope this is a valid handle 247 | if (!$userGroup = craft()->userGroups->getGroupByHandle($source)) { 248 | return false; 249 | } 250 | 251 | $userGroupId = $userGroup->id; 252 | 253 | } else { 254 | 255 | $temp = explode(':', $source); 256 | 257 | if ($temp[0] != 'group' || !isset($temp[1]) || !is_numeric($temp[1])) { 258 | return false; 259 | } 260 | 261 | $userGroupId = $temp[1]; 262 | 263 | } 264 | 265 | $layout = $this->getLayoutByUserGroupId($userGroupId); 266 | 267 | } 268 | 269 | return $layout; 270 | 271 | } 272 | 273 | /* 274 | * Returns layout from section ID 275 | * 276 | */ 277 | public function getLayoutBySectionId($sectionId) 278 | { 279 | $layouts = $this->getLayouts(); 280 | foreach ($layouts as $layout) { 281 | if ($layout->sectionId === $sectionId) { 282 | return $layout; 283 | } 284 | } 285 | return false; 286 | } 287 | 288 | /* 289 | * Returns layout from category group ID 290 | * 291 | */ 292 | public function getLayoutByCategoryGroupId($categoryGroupId) 293 | { 294 | $layouts = $this->getLayouts(); 295 | foreach ($layouts as $layout) { 296 | if ($layout->categoryGroupId === $categoryGroupId) { 297 | return $layout; 298 | } 299 | } 300 | return false; 301 | } 302 | 303 | /* 304 | * Returns layout from asset source ID 305 | * 306 | */ 307 | public function getLayoutByAssetSourceId($assetSourceId) 308 | { 309 | $layouts = $this->getLayouts(); 310 | foreach ($layouts as $layout) { 311 | if ($layout->assetSourceId === $assetSourceId) { 312 | return $layout; 313 | } 314 | } 315 | return false; 316 | } 317 | 318 | /* 319 | * Returns layout from user group ID 320 | * 321 | */ 322 | public function getLayoutByUserGroupId($userGroupId) 323 | { 324 | $layouts = $this->getLayouts(); 325 | foreach ($layouts as $layout) { 326 | if ($layout->userGroupId === $userGroupId) { 327 | return $layout; 328 | } 329 | } 330 | return false; 331 | } 332 | 333 | /* 334 | * Returns layout from listing handle 335 | * 336 | */ 337 | public function getLayoutByListingHandle($listingHandle) 338 | { 339 | $layouts = $this->getLayouts(); 340 | foreach ($layouts as $layout) { 341 | if ($layout->listingHandle === $listingHandle) { 342 | return $layout; 343 | } 344 | } 345 | return false; 346 | } 347 | 348 | /* 349 | * Save layout to db 350 | * 351 | */ 352 | public function saveLayout(DashCols_LayoutModel $dashColsLayout) 353 | { 354 | 355 | $existingLayout = false; 356 | 357 | if ($dashColsLayout->id) { 358 | if (!$dashColsLayoutRecord = DashCols_LayoutRecord::model()->findById($dashColsLayout->id)) { 359 | throw new Exception(Craft::t('Could not find layout with ID "{id}"', array( 360 | 'id' => $dashColsLayout->id, 361 | ))); 362 | } 363 | $existingLayout = DashCols_LayoutModel::populateModel($dashColsLayoutRecord); 364 | } else { 365 | $dashColsLayoutRecord = new DashCols_LayoutRecord(); 366 | } 367 | 368 | if ($dashColsLayout->sectionId) { 369 | $dashColsLayoutRecord->sectionId = $dashColsLayout->sectionId; 370 | } else if ($dashColsLayout->categoryGroupId) { 371 | $dashColsLayoutRecord->categoryGroupId = $dashColsLayout->categoryGroupId; 372 | } else if ($dashColsLayout->assetSourceId) { 373 | $dashColsLayoutRecord->assetSourceId = $dashColsLayout->assetSourceId; 374 | } else if ($dashColsLayout->userGroupId) { 375 | $dashColsLayoutRecord->userGroupId = $dashColsLayout->userGroupId; 376 | } else if ($dashColsLayout->listingHandle) { 377 | $dashColsLayoutRecord->listingHandle = $dashColsLayout->listingHandle; 378 | } else { 379 | throw new Exception(Craft::t('Unknown target for layout')); 380 | } 381 | 382 | $dashColsLayoutRecord->hiddenFields = $dashColsLayout->hiddenFields; 383 | $dashColsLayoutRecord->metaFields = $dashColsLayout->metaFields; 384 | 385 | $dashColsLayoutRecord->validate(); 386 | 387 | $dashColsLayout->addErrors($dashColsLayoutRecord->getErrors()); 388 | 389 | if (!$dashColsLayout->hasErrors()) { 390 | 391 | $transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null; 392 | 393 | try { 394 | 395 | if ($existingLayout && $existingLayout->fieldLayoutId) { 396 | craft()->fields->deleteLayoutById($existingLayout->fieldLayoutId); 397 | } 398 | 399 | $fieldLayout = $dashColsLayout->getFieldLayout(); 400 | craft()->fields->saveLayout($fieldLayout); 401 | 402 | $dashColsLayout->fieldLayoutId = $fieldLayout->id; 403 | $dashColsLayoutRecord->fieldLayoutId = $fieldLayout->id; 404 | 405 | if (!$dashColsLayout->id) { 406 | $dashColsLayoutRecord->save(); 407 | } else { 408 | $dashColsLayoutRecord->update(); 409 | } 410 | 411 | $dashColsLayout->id = $dashColsLayoutRecord->id; 412 | 413 | if ($transaction !== null) { 414 | $transaction->commit(); 415 | } 416 | 417 | } catch (\Exception $e) { 418 | 419 | if ($transaction !== null) { 420 | $transaction->rollback(); 421 | } 422 | 423 | throw $e; 424 | 425 | } 426 | 427 | return true; 428 | 429 | } 430 | 431 | return false; 432 | 433 | } 434 | 435 | } -------------------------------------------------------------------------------- /dashcols/templates/_layouts/_edit.twig: -------------------------------------------------------------------------------- 1 | {% extends '_layouts/cp' %} 2 | {% import '_includes/forms' as forms %} 3 | 4 | {% set title = craft.dashCols.getPluginName | t %} 5 | 6 | {% block content %} 7 | 8 | {% if tabNav is defined and tabNav|length %} 9 | 18 | {% endif %} 19 | 20 |
21 |
22 | 23 | {% include '_includes/fieldlayoutdesigner' with { 24 | fieldLayout : layout.getFieldLayout(), 25 | customizableTabs : false, 26 | pretendTabName : 'Columns' | t 27 | } only %} 28 | 29 |
30 | 31 | {% if defaultFields is defined and defaultFields | length > 0 and defaultFields != false %} 32 |

Hide/show default fields

33 | 34 | 35 | {% for hiddenFieldKey,hiddenFieldValue in defaultFields %} 36 | 37 | 46 | 47 | 48 | {% endfor %} 49 | 50 |
38 |
39 | {{ forms.lightswitch({ 40 | id: hiddenFieldKey, 41 | name: 'hiddenFields[' ~ hiddenFieldKey ~ ']', 42 | on : hiddenFieldKey in layout.hiddenFields ? false : true, 43 | }) }} 44 |
45 |
51 |
52 | {% endif %} 53 | 54 | {% if metaFields is defined and metaFields | length > 0 and metaFields != false %} 55 |

Show/hide metadata

56 | 57 | 58 | {% for metaFieldKey,metaFieldValue in metaFields %} 59 | 60 | 69 | 70 | 71 | {% endfor %} 72 | 73 |
61 |
62 | {{ forms.lightswitch({ 63 | id: metaFieldKey, 64 | name: 'metaFields[' ~ metaFieldKey ~ ']', 65 | on : metaFieldKey in layout.metaFields ? true : false, 66 | }) }} 67 |
68 |
74 |
75 | {% endif %} 76 | 77 |
78 | 79 |
80 | 81 | {% if section is defined %} 82 | 83 | {% elseif categoryGroup is defined %} 84 | 85 | {% elseif assetSource is defined %} 86 | 87 | {% elseif userGroup is defined %} 88 | 89 | {% elseif listingHandle is defined %} 90 | 91 | {% endif %} 92 | 93 | {% if layout.id %} 94 | 95 | {% endif %} 96 | 97 | {% if redirectUrl %} 98 | 99 | {% endif %} 100 | 101 | 102 | 103 | 104 |
105 |
106 | 107 | {% include 'dashcols/_partials/_footer.twig' %} 108 | 109 | {% endblock %} -------------------------------------------------------------------------------- /dashcols/templates/_layouts/index.twig: -------------------------------------------------------------------------------- 1 | {% extends '_layouts/cp' %} 2 | 3 | {% set title = craft.dashCols.getPluginName | t %} 4 | {% set selectedTab = 'dashColsIndex' %} 5 | 6 | {% set content %} 7 | 8 | {% if craft.dashCols.isCraftRequiredVersion == false %} 9 | 10 |

Sorry, you need to run Craft {{ craft.dashCols.requiredCraftVersion }} or newer to use DashCols.
Please update your Craft installation.

11 | 12 | {% else %} 13 | 14 |
15 | 16 |

{{ 'Entries' | t }}

17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 29 | 30 | 31 |
22 | {{ 'All entries' | t }} 23 |
27 | {{ 'Singles' | t }} 28 |
32 | 33 | {% if channels %} 34 | 35 | 36 | 37 | 38 | 39 | {% for channel in channels %} 40 | 41 | 44 | 45 | {% endfor %} 46 | 47 |
{{ 'Channels' | t }}
42 | {{ channel.name | t }} 43 |
48 | {% endif %} 49 | 50 | {% if structures %} 51 | 52 | 53 | 54 | 55 | 56 | {% for structure in structures %} 57 | 58 | 61 | 62 | {% endfor %} 63 | 64 |
{{ 'Structures' | t }}
59 | {{ structure.name | t }} 60 |
65 | {% endif %} 66 | 67 |
68 | 69 |
70 | 71 | {% if categoryGroups %} 72 | 73 |
74 | 75 |

{{ 'Categories' | t }}

76 | 77 | 78 | 79 | 80 | 81 | 82 | {% for categoryGroup in categoryGroups %} 83 | 84 | 87 | 88 | {% endfor %} 89 | 90 |
{{ 'Category groups' | t }}
85 | {{ categoryGroup.name | t }} 86 |
91 | 92 |
93 | 94 |
95 | 96 | {% endif %} 97 | 98 | {% if assetSources %} 99 | 100 |
101 | 102 |

{{ 'Assets' | t }}

103 | 104 | 105 | 106 | 107 | 108 | 109 | {% for assetSource in assetSources %} 110 | 111 | 114 | 115 | {% endfor %} 116 | 117 |
{{ 'Asset sources' | t }}
112 | {{ assetSource.name | t }} 113 |
118 | 119 |
120 | 121 |
122 | 123 | {% endif %} 124 | 125 |
126 | 127 |

Users

128 | 129 | 130 | 131 | 132 | 135 | 136 | 137 |
133 | {{ 'All Users' | t }} 134 |
138 | 139 | {% if userGroups %} 140 | 141 | 142 | 143 | 144 | 145 | {% for userGroup in userGroups %} 146 | 147 | 150 | 151 | {% endfor %} 152 | 153 |
{{ 'User groups' | t }}
148 | {{ userGroup.name | t }} 149 |
154 | 155 | {% endif %} 156 | 157 |
158 | 159 |
160 | 161 | {% endif %} 162 | 163 | {% include 'dashcols/_partials/_footer.twig' %} 164 | 165 | {% endset %} -------------------------------------------------------------------------------- /dashcols/templates/_legacy.twig: -------------------------------------------------------------------------------- 1 | {% extends '_layouts/cp' %} 2 | {% import '_includes/forms' as forms %} 3 | 4 | {% set title = craft.dashCols.getPluginName | t %} 5 | 6 | {% block content %} 7 | 8 |

Sorry, you need to run Craft {{ craft.dashCols.requiredCraftVersion }} or newer to use DashCols.
Please update Craft and try again!

9 | 10 | {% include 'dashcols/_partials/_footer.twig' %} 11 | 12 | {% endblock %} -------------------------------------------------------------------------------- /dashcols/templates/_partials/_footer.twig: -------------------------------------------------------------------------------- 1 |
2 |

{{ craft.dashCols.getPluginName }} v. {{ craft.dashCols.version }} • AboutSettingsGitHub

3 |
-------------------------------------------------------------------------------- /dashcols/templates/about.twig: -------------------------------------------------------------------------------- 1 | {% extends '_layouts/cp' %} 2 | 3 | {% set title = craft.dashCols.getPluginName | t %} 4 | {% set tabs = craft.dashCols.getCpTabs %} 5 | {% set selectedTab = 'about' %} 6 | 7 | {% block content %} 8 | 9 |
10 | 11 |

{{ 'About DashCols' | t }} v. {{ craft.dashCols.version }}

12 | 13 |

{{ 'DashCols makes it easy to include your custom fields in element index tables.' | t }}

14 | 15 |

{{ craft.dashCols.getPluginUrl }}

16 | 17 | {% if craft.dashCols.isCraftRequiredVersion == false %} 18 |

Important: {{ craft.dashCols.getPluginName }} requires Craft {{ craft.dashCols.requiredCraftVersion }} or newer. You'll need to update Craft to use this plugin.

19 | {% endif %} 20 | 21 |
22 | 23 |
24 | 25 |

{{ 'Supported fields' | t }}

26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 91 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 | 121 | 122 | 123 | 124 |
{{ 'Field type' | t }}{{ 'Notes' | t }}
35 | Assets 36 | DashCols will only display the first Asset in a field in full (thumbnail if jpg/gif/png or icon + filename if other filetype), in addition to the the total filecount (if more than 1 Asset in field)
41 | Categories 42 | Outputs as a comma-separated list
47 | Checkboxes 48 |
53 | Color 54 | Outputs as a colored rectangle
59 | Date/time 60 | Outputs date and/or time based on field settings
65 | Dropdown 66 |
71 | Entries 72 | Outputs as a comma-separated list
77 | Lightswitch 78 |
83 | Multi-select 84 | Selected values output as a comma-separated list
89 | Number 90 |
95 | Plain Text 96 |
101 | Position Select 102 |
107 | Radio Buttons 108 |
113 | Tags 114 | Outputs as a comma-separated list
119 | Users 120 | Outputs as a comma-separated list
125 | 126 |
127 | 128 |

Unsupported fields

129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
{{ 'Field type' | t }}
Matrix
Rich Text
Table
149 | 150 |
151 | 152 |

Custom FieldTypes

153 | 154 |

Please note that while any attribute of String value will output, you're currently on your own regarding custom FieldTypes.

155 | 156 |
157 | 158 |
159 | 160 |
161 | 162 |

{{ 'Changelog' | t }}

163 | 164 |
Version 1.3 – 11.04.15
165 |
    166 |
  • Now supports editing columns for Users – big thanks to Lindsey DiLoreto for the help!
  • 167 |
  • Now supports editing columns for Assets
  • 168 |
  • CP section redesigned
  • 169 |
  • Fixes issue with the _Edit columns_ button not always appearing
  • 170 |
  • Various small improvements and fixes
  • 171 |
172 | 173 |
Version 1.2.5 - 09.23.15
174 |
    175 |
  • Fixed issue where numeric columns wouldn't render zeros
  • 176 |
177 | 178 |
Version 1.2.4 - 09.08.15
179 |
    180 |
  • Added support for Doxter
  • 181 |
  • Slightly improved support for complex custom fieldtypes in general
  • 182 |
  • Removed custom plugin name setting
  • 183 |
184 | 185 |
Version 1.2.3 - 06.03.15
186 |
    187 |
  • SVG assets now display as thumbnails for Craft 2.4 builds (only where ImageMagick is installed)
  • 188 |
189 | 190 |
Version 1.2.2 - 04.20.15
191 |
    192 |
  • Fixed bug where the _Structure_ sorting option was hidden
  • 193 |
194 | 195 |
Version 1.2.1 – 10.04.15
196 |
    197 |
  • Added Entry Type metadata column
  • 198 |
199 | 200 |
Version 1.2 – 06.04.15
201 |
    202 |
  • Added sorting capabilities for FieldTypes of Boolean, String, Number or DateTime value
  • 203 | 204 | 205 |
    Version 1.1.9 – 04.03.15
    206 |
      207 |
    • Added option to output element metadata (Updated Date, ID and Author) as columns
    • 208 |
    • Fixed issue where string values "1" and "0" would always render as a Lightswitch attribute – thanks Fred, you're a champ.
    • 209 |
    • Fixed issue where string values starting with "#" would sometimes render as a Color attribute
    • 210 |
    • String values interpreted as external links will now open in a new tab
    • 211 |
    • Added setting for renaming DashCols
    • 212 | 213 | 214 |
      Version 1.1.8 - 03.06.15
      215 |
        216 |
      • Asset columns will now display total number of files (if more than 1)
      • 217 |
      • Asset columns now display icon + filename for files.
      • 218 |
      219 | 220 |
      Version 1.1.7 - 02.25.15
      221 | 222 |
        223 |
      • Fixed issue where layouts would not save if CSRF is enabled
      • 224 |
      225 | 226 |
      Version 1.1.6 - 02.23.15
      227 | 228 |
        229 |
      • Minor refactor
      • 230 |
      231 | 232 |
      Version 1.1.5 - 02.23.15
      233 | 234 |
        235 |
      • Fixed issue where the Edit Columns button would not be added to non-managed index tables
      • 236 |
      • Date/Time columns now display date and/or time based on field settings
      • 237 |
      238 | 239 |
      Version 1.1.4 - 02.13.15
      240 |
        241 |
      • Added settings page and the Undercover mode setting to hide the CP section and disable layout editing
      • 242 |
      243 | 244 |
      Version 1.1.3 – 02.13.15
      245 |
        246 |
      • Fixed issue w/ redirect on All entries layout save
      • 247 |
      • Edit template only displays relevant default fields
      • 248 |
      • Removed editing for individual Single sections
      • 249 |
      • Added footer CP section footer
      • 250 |
      251 | 252 |
      Version 1.1.2 – 02.12.15
      253 |
        254 |
      • Fixed issue w/ 404 for Singles listing redirect on save
      • 255 |
      256 | 257 |
      Version 1.1.1 – 02.12.15
      258 |
        259 |
      • Hotfixed an annoying issue w/ move icons not being vertically centered in tall table rows
      • 260 |
      261 | 262 |
      Version 1.1 – 02.12.15
      263 |
        264 |
      • Added option to hide default fields (postDate, expiryDate, URI and section)
      • 265 |
      • (Hopefully) improved CP section sub nav
      • 266 |
      • DashCols now redirects to index table upon saving a layout
      • 267 |
      • Some minor CSS fixes here and there
      • 268 |
      269 | 270 |
      Version 1.0 – 02.08.15
      271 |

      Initial public release

      272 | 273 |
274 | 275 |
276 | 277 |
278 | 279 |

{{ 'Roadmap' | t }}

280 | 281 |

Look for the following in future updates:

282 | 283 |
    284 |
  • Sorting/ordering options
  • 285 |
  • Support for User tables
  • 286 |
  • Inline editing of sections/listings/category groups
  • 287 |
  • Option to clear a layout w/ a single button
  • 288 |
  • Optional output of element metadata
  • 289 |
290 | 291 |

Unfortunately, I can offer no ETA on any of these additions.

292 | 293 |
294 | 295 |
296 | 297 |
298 |

{{ 'Disclaimer' | t }}

299 | 300 |

DashCols is provided free of charge. The author is not responsible for any data loss or other problems resulting from the use of this plugin.

301 | 302 |

Please report any bugs, feature requests or other issues here. As DashCols is a hobby project, no promises are made regarding response time, feature implementations or bug amendments.

303 | 304 |

Pull requests are very welcome!

305 |
306 | 307 |
308 | 309 | {% include 'dashcols/_partials/_footer.twig' %} 310 | 311 | {% endblock %} 312 | -------------------------------------------------------------------------------- /dashcols/templates/settings.twig: -------------------------------------------------------------------------------- 1 | {% import '_includes/forms' as forms %} 2 | 3 |
4 |
5 | 6 |
7 |

Keeps DashCols running, but disables access to the CP section and layout editing for all users.

8 |
9 | {{ forms.lightswitch({ 10 | id: 'cpSectionDisabled', 11 | name: 'cpSectionDisabled', 12 | on : settings.cpSectionDisabled, 13 | }) }} 14 |
15 |
16 | -------------------------------------------------------------------------------- /dashcols/variables/DashColsVariable.php: -------------------------------------------------------------------------------- 1 | 7 | * @package DashCols 8 | * @since Craft 2.3 9 | * @copyright Copyright (c) 2015, Mats Mikkel Rummelhoff 10 | * @license http://opensource.org/licenses/mit-license.php MIT License 11 | * @link https://github.com/mmikkel/dashcols-craft 12 | */ 13 | 14 | class DashColsVariable 15 | { 16 | 17 | private $_plugin = null; 18 | 19 | public function getPlugin() 20 | { 21 | return craft()->dashCols->getPlugin(); 22 | } 23 | 24 | public function getCpTabs() 25 | { 26 | return craft()->dashCols->getCpTabs(); 27 | } 28 | 29 | public function getPluginUrl() 30 | { 31 | return $this->getPlugin()->getPluginUrl(); 32 | } 33 | 34 | public function getPluginName() 35 | { 36 | return $this->getPlugin()->getName(); 37 | } 38 | 39 | public function version() 40 | { 41 | return $this->getPlugin()->getVersion(); 42 | } 43 | 44 | public function requiredCraftVersion() 45 | { 46 | return $this->getPlugin()->getCraftRequiredVersion(); 47 | } 48 | 49 | public function isCraftRequiredVersion() 50 | { 51 | return $this->getPlugin()->isCraftRequiredVersion(); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /source/demo/cp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmikkel/DashCols-Craft/42b0d1b0150db4c790d0b90a4e7cd1366226bb08/source/demo/cp.jpg -------------------------------------------------------------------------------- /source/demo/index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmikkel/DashCols-Craft/42b0d1b0150db4c790d0b90a4e7cd1366226bb08/source/demo/index.jpg -------------------------------------------------------------------------------- /source/demo/sort.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmikkel/DashCols-Craft/42b0d1b0150db4c790d0b90a4e7cd1366226bb08/source/demo/sort.jpg -------------------------------------------------------------------------------- /source/src/scripts/dashcols.cp.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | 3 | if (!window.$) { 4 | return; 5 | } 6 | 7 | var DashCols_CpSection = {}; 8 | 9 | DashCols_CpSection.init = function () { 10 | 11 | // Init submit button 12 | var $submitBtn = $('#dashCols-actions .submit:first'); 13 | if ($submitBtn.length > 0) $submitBtn.on('click', $.proxy(onSubmitButtonClick, this)); 14 | 15 | // Init tab nav select 16 | var $tabNav = $('#dashCols-editNavSelect'); 17 | if ($tabNav.length > 0) $tabNav.on('change', $.proxy(onTabNavChange, this)); 18 | 19 | } 20 | 21 | function onSubmitButtonClick (e) { 22 | 23 | // Where are we? 24 | var path = Craft.path.replace('dashcols/layouts/', ''), 25 | segments = path.split('/'); 26 | 27 | if (segments[0] === 'dashcols') segments.shift(); 28 | 29 | if (segments.length === 0) return false; 30 | 31 | // Set cached element index to the current section or category group 32 | switch (segments[0]) { 33 | 34 | case 'entries' : 35 | 36 | var selectedSource = '*', 37 | sectionId = $('input[name="sectionId"]').val(); 38 | 39 | if (sectionId !== undefined) 40 | { 41 | selectedSource = 'section:' + sectionId; 42 | } 43 | else 44 | { 45 | selectedSource = segments.length > 1 && segments[1] === 'singles' ? 'singles' : '*'; 46 | } 47 | 48 | Craft.setLocalStorage('elementindex.Entry', { 49 | selectedSource : selectedSource 50 | }); 51 | 52 | console.log('selected source', selectedSource); 53 | 54 | break; 55 | 56 | case 'categories' : 57 | 58 | var groupId = $('input[name="categoryGroupId"]').val(); 59 | 60 | if (groupId !== undefined) 61 | { 62 | Craft.setLocalStorage('elementindex.Category', { 63 | selectedSource : 'group:' + groupId 64 | }); 65 | } 66 | else 67 | { 68 | Craft.setLocalStorage('elementindex.Category', ''); 69 | } 70 | 71 | console.log('selected source', groupId); 72 | 73 | break; 74 | 75 | case 'users' : 76 | 77 | var groupId = $('input[name="userGroupId"]').val(); 78 | 79 | if (groupId !== undefined) 80 | { 81 | Craft.setLocalStorage('elementindex.User', { 82 | selectedSource : 'group:' + groupId 83 | }); 84 | } 85 | else 86 | { 87 | Craft.setLocalStorage('elementindex.User', ''); 88 | } 89 | 90 | console.log('selected source', groupId); 91 | 92 | break; 93 | 94 | case 'assets' : 95 | 96 | var folderId = $('input[name="assetSourceId"]').val(); 97 | 98 | if (folderId !== undefined) 99 | { 100 | Craft.setLocalStorage('elementindex.Asset', { 101 | selectedSource : 'folder:' + folderId 102 | }); 103 | } 104 | else 105 | { 106 | Craft.setLocalStorage('elementindex.Asset', ''); 107 | } 108 | 109 | console.log('selected source', folderId); 110 | 111 | break; 112 | 113 | } 114 | 115 | } 116 | 117 | function onTabNavChange (e) 118 | { 119 | e.preventDefault(); 120 | window.location.href = $(e.target).val(); 121 | } 122 | 123 | $(document).ready($.proxy(DashCols_CpSection.init, DashCols_CpSection)); 124 | 125 | } (window)); -------------------------------------------------------------------------------- /source/src/scripts/dashcols.index.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | 3 | if (!window.$) { 4 | return; 5 | } 6 | 7 | var DashCols_Index = { 8 | $sortButton : null 9 | }; 10 | 11 | DashCols_Index.init = function () { 12 | 13 | var updateHandler = $.proxy(onUpdate, this), 14 | resizeHandler = $.proxy(onResize, this); 15 | 16 | $(document).ajaxComplete(updateHandler); 17 | $(window).on('resize', resizeHandler); 18 | 19 | } 20 | 21 | DashCols_Index.evalResponsiveTable = function () { 22 | 23 | // Get DOM elements 24 | var $tableView = $('#content .tableview:first'), 25 | $table = $('#content .tableview:first table:first'); 26 | 27 | if ($tableView.length === 0 || $table.length === 0) { 28 | return false; 29 | } 30 | 31 | if ($table.outerWidth() > $tableView.outerWidth()) { 32 | $tableView.addClass('dashCols-scrollable'); 33 | } else { 34 | $tableView.removeClass('dashCols-scrollable'); 35 | } 36 | 37 | } 38 | 39 | DashCols_Index.updateEditButton = function () { 40 | 41 | var $editButton = $('#dashCols-editButton'), 42 | hasEditButton = $editButton.length > 0; 43 | 44 | if (hasEditButton) 45 | { 46 | if (this.editUrl) $editButton.attr('href', this.editUrl); 47 | else $editButton.remove(); 48 | } 49 | else if(this.editUrl) 50 | { 51 | var editButtonHtml = 'Edit columns'; 52 | $('#content').append(editButtonHtml); 53 | } 54 | 55 | } 56 | 57 | DashCols_Index.updateSortButton = function () 58 | { 59 | 60 | if (this.$sortButton === null) { 61 | this.$sortButton = $('.sortmenubtn:first'); 62 | if (this.$sortButton.length > 0) { 63 | this.$sortButton.on('click mouseenter', $.proxy(onSortMenuButtonClick, this)); 64 | } 65 | } 66 | 67 | if (this.$sortButton.length === 0) { 68 | return false; 69 | } 70 | 71 | this.updateSortMenu(); 72 | 73 | } 74 | 75 | DashCols_Index.updateSortMenu = function () { 76 | 77 | var $sortAttributes = $('.menu ul.sort-attributes:first'); 78 | 79 | if ($sortAttributes.length === 0) { 80 | requestAnimationFrame($.proxy(this.updateSortMenu, this)); 81 | return false; 82 | } 83 | 84 | var $sortAttributesItems = $sortAttributes.find('li'), 85 | $sortAttributeItem, 86 | $indexTableColumns = $('.tableview .data th'), 87 | attribute, 88 | attributeValue, 89 | attributes = []; 90 | 91 | $indexTableColumns.each(function () { 92 | attribute = $(this).data('attribute') || false; 93 | if (attribute) { 94 | attributes.push(attribute); 95 | } 96 | }); 97 | 98 | $sortAttributesItems.show().each(function () { 99 | $sortAttributeItem = $(this); 100 | attributeValue = $sortAttributeItem.find('a:first').data('attr'); 101 | if (attributeValue !== 'structure' && $.inArray(attributeValue, attributes) === -1) { 102 | $sortAttributeItem.hide(); 103 | } 104 | }); 105 | 106 | } 107 | 108 | function onUpdate(e, status, requestData) { 109 | 110 | this.editUrl = Craft.baseCpUrl + '/dashcols/', 111 | this.entryIndex = false; 112 | 113 | if (requestData.url.indexOf('elementIndex/getElements') === -1) { 114 | return false; 115 | } 116 | 117 | // Quo vadis? 118 | var currentUrl = e ? e.target.URL : window.location.href, 119 | uri = currentUrl.replace(Craft.baseCpUrl, ''), 120 | segments = uri.split('/'); 121 | 122 | if (segments[0].length === 0) { 123 | segments.shift(); 124 | } 125 | 126 | switch (segments[0]) { 127 | 128 | case 'entries' : 129 | 130 | if (!segments[1]) { 131 | this.editUrl += 'entries'; 132 | } else if (segments[1] === 'singles') { 133 | this.editUrl += 'entries/singles'; 134 | } else { 135 | this.editUrl += 'entries/section/' + segments[1] || ''; 136 | } 137 | 138 | this.entryIndex = Craft.EntryIndex || false; 139 | 140 | break; 141 | 142 | case 'categories' : 143 | 144 | this.editUrl += 'categories/' + (segments[1] || ''); 145 | this.entryIndex = Craft.CategoryIndex || false; 146 | 147 | break; 148 | 149 | case 'users' : 150 | 151 | var source = Craft.getLocalStorage('elementindex.User').selectedSource, 152 | groupId = source.replace(/^group:/, ''); 153 | 154 | this.editUrl += segments[0] + (!isNaN(groupId) ? '/' + groupId : ''); 155 | this.entryIndex = Craft.UserIndex || false; 156 | 157 | break; 158 | 159 | case 'assets' : 160 | 161 | var source = Craft.getLocalStorage('elementindex.Asset').selectedSource, 162 | folderId = source.replace(/^folder:/, ''); 163 | 164 | this.editUrl += segments[0] + (!isNaN(folderId) ? '/' + folderId : ''); 165 | this.entryIndex = Craft.AssetIndex || false; 166 | 167 | break; 168 | 169 | } 170 | 171 | this.evalResponsiveTable(); 172 | 173 | // Return if CP Section is disabled 174 | var dashColsSettings = window._DashCols || {}; 175 | if (dashColsSettings.hasOwnProperty('cpSectionDisabled') && dashColsSettings.cpSectionDisabled) return false; 176 | 177 | this.updateEditButton(); 178 | this.updateSortButton(); 179 | 180 | } 181 | 182 | function onResize(e) { 183 | this.evalResponsiveTable(); 184 | } 185 | 186 | function onSortMenuButtonClick(e) { 187 | this.updateSortMenu(); 188 | } 189 | 190 | $(document).ready($.proxy(DashCols_Index.init, DashCols_Index)); 191 | 192 | } (window)); -------------------------------------------------------------------------------- /source/src/styles/dashcols.cp.scss: -------------------------------------------------------------------------------- 1 | .tab#tab-dashColsIndex { 2 | &:before { 3 | content: "home"; 4 | font-family: 'Craft'; 5 | speak: none; 6 | font-feature-settings: "liga", "dlig"; 7 | text-rendering: optimizeLegibility; 8 | font-weight: normal; 9 | font-variant: normal; 10 | text-transform: none; 11 | -webkit-font-smoothing: anti-aliased; 12 | -moz-osx-font-smoothing: grayscale; 13 | user-select: none; 14 | word-wrap: normal !important; 15 | line-height: 1; 16 | position: relative; 17 | top: 1px; 18 | } 19 | } 20 | 21 | .dashCols-layoutGroupTable { 22 | margin-bottom: 20px; 23 | } 24 | 25 | /* 26 | * About page 27 | * 28 | */ 29 | .dashCols-aboutPage td.desc { 30 | color: rgba( #000, .5 ); 31 | } 32 | 33 | /* 34 | * Edit template 35 | * 36 | */ 37 | #dashCols-sectionEdit, 38 | #dashCols-defaultFields { 39 | width: 100%; 40 | float: left; 41 | clear: both; 42 | margin-bottom: 20px; 43 | } 44 | 45 | #dashCols-actions { 46 | width: 100%; 47 | float: left; 48 | clear: both; 49 | } 50 | 51 | #dashCols-sections { 52 | width: 100%; 53 | clear: both; 54 | float: left; 55 | ul { 56 | list-style: none; 57 | margin: 0; padding: 0; 58 | } 59 | li { 60 | display: inline-block; 61 | width: auto; 62 | float: left; 63 | margin: 0 10px 10px 0; 64 | } 65 | } 66 | 67 | #dashCols-sectionEdit { 68 | width: 100%; 69 | clear: both; 70 | float: left; 71 | } 72 | 73 | /* 74 | * CP section footer 75 | * 76 | */ 77 | #dashCols-colophone { 78 | width: 100%; 79 | float: left; 80 | clear: both; 81 | font-size: 11px; 82 | color: #8f98a3; 83 | text-align: right; 84 | margin-top: 20px; 85 | } -------------------------------------------------------------------------------- /source/src/styles/dashcols.index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Index table styles 3 | * 4 | */ 5 | #dashCols-editButton { 6 | margin-top: 10px; 7 | } 8 | 9 | .dashCols-scrollable { 10 | overflow-x: auto; 11 | } 12 | 13 | .elements.datatablesorthelper .move.icon, 14 | .elementindex .move.icon { 15 | top: 50% !important; 16 | margin-top: -12px !important; 17 | } 18 | 19 | /* 20 | * Fields 21 | * 22 | */ 23 | .dashCols-lightswitch { 24 | font-weight: bold; 25 | padding-left: 20px; 26 | font-size: 12px; 27 | } 28 | .dashCols-lightswitchOn { 29 | &:before { 30 | content: "✔"; 31 | } 32 | } 33 | .dashCols-lightswitchOff { 34 | &:before { 35 | content: "x"; 36 | } 37 | } 38 | 39 | .dashCols-color { 40 | display: block; 41 | border-radius: 2px; 42 | width: 15px; height: 15px; 43 | border: 1px solid #DDDFE0; 44 | padding: 2px; 45 | overflow: hidden; 46 | } 47 | 48 | .dashCols-color .dashCols-hex { 49 | display: block; 50 | width: 100%; height: 100%; 51 | } 52 | 53 | .dashCols-positionSelect { 54 | text-transform: capitalize; 55 | } 56 | 57 | .dashCols-assetFieldImage { 58 | width: auto; 59 | float: left; 60 | max-width: 100%; 61 | position: relative; 62 | border-radius: 2px; 63 | overflow: hidden; 64 | } 65 | 66 | .dashCols-assetFieldImage img { 67 | display: block; 68 | float: left; 69 | } 70 | 71 | .dashCols-assetFieldImage .dashCols-assetFileCount { 72 | box-sizing: border-box; 73 | width: 100%; 74 | position: absolute; 75 | left: 0; bottom: 0; 76 | background-color: #333F4D; 77 | top: auto; 78 | color: #d7d9db; 79 | padding: 2px 2px 4px 4px; 80 | } 81 | 82 | .dashCols-assetFile { 83 | width: 100%; 84 | float: left; 85 | clear: both; 86 | } 87 | 88 | .dashCols-assetFileIcon { 89 | display: inline-block; 90 | margin-right: 2px; 91 | } 92 | 93 | .dashCols-assetFilename { 94 | font-family: "Lucida Console", Monaco, monospace; 95 | font-size: 10px; 96 | position: relative; 97 | top: -6px; 98 | } 99 | 100 | .dashCols-assetFileCount { 101 | display: block; 102 | width: 100%; 103 | clear: both; 104 | font-size: 10px; 105 | color: rgba( 0, 0, 0, 0.5 ); 106 | line-height: 1; 107 | position: relative; 108 | top: -2px; 109 | } -------------------------------------------------------------------------------- /source/tasks/Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* global module: false, process: false */ 2 | 3 | /* ======================================================= 4 | DASHCOLS GRUNTFILE 5 | 6 | @version 1.0.0 7 | * ======================================================= */ 8 | 9 | module.exports = function ( grunt ) { 10 | 11 | if ( grunt.option( 'time' ) !== undefined && grunt.option( 'time' ) ) { 12 | require( 'time-grunt' )( grunt ); 13 | } 14 | 15 | var devDir = '../src/', 16 | buildDir = '../../dashcols/resources/', 17 | pkg = grunt.file.readJSON( 'package.json' ), 18 | banner = '/*! <%= pkg.name %> | @author <%= pkg.author %> | <%= grunt.template.today("dd-mm-yyyy") %> */\n', 19 | 20 | options = { 21 | 22 | banner : banner, 23 | pkg : pkg, 24 | 25 | isDev : ( grunt.option( 'dev' ) !== undefined ) ? Boolean( grunt.option( 'dev' ) ) : process.env.GRUNT_ISDEV === '1', 26 | 27 | devDir : devDir, 28 | buildDir : buildDir, 29 | 30 | devJsDir : devDir + 'scripts/', 31 | devCssDir : devDir + 'styles/', 32 | devImgDir : devDir + 'images/', 33 | devFontsDir : devDir + 'webfonts/', 34 | devVendorDir : devDir + 'vendor/', 35 | 36 | buildJsDir : buildDir + 'js/', 37 | buildCssDir : buildDir + 'css/', 38 | buildImgDir : buildDir + 'img/', 39 | buildFontsDir : buildDir + 'fonts/', 40 | buildVendorDir : buildDir + 'vendor/', 41 | 42 | }; 43 | 44 | /* 45 | * Load tasks 46 | */ 47 | require( 'load-grunt-config' )( grunt, { 48 | configPath : require( 'path' ).join( process.cwd(), 'grunt-configs' ), 49 | data : options 50 | } ); 51 | 52 | if ( options.isDev ) { 53 | grunt.log.subhead( 'Running Grunt in DEV mode' ); 54 | } 55 | 56 | /* 57 | * Register tasks 58 | */ 59 | 60 | grunt.registerTask( 'build', [ 'clean:build', 'compile:css', 'compile:js', 'copy:vendor', 'growl:complete' ] ); 61 | grunt.registerTask( 'default', [ 'build', 'watch' ] ); 62 | 63 | /* 64 | * Stylesheets 65 | */ 66 | grunt.registerTask( 'compile:css', function () { 67 | 68 | grunt.task.run( [ 'sass', 'autoprefixer' ] ); 69 | 70 | // if ( ! options.isDev ) { 71 | // grunt.task.run( [ 'cssmin' ] ); 72 | // } 73 | 74 | } ); 75 | 76 | grunt.registerTask( 'lint:css', [ 'newer:scsslint' ] ); 77 | 78 | /* 79 | * Javascripts 80 | */ 81 | grunt.registerTask( 'compile:js', function () { 82 | 83 | grunt.task.run( [ 'lint:js', 'copy:js' ] ); 84 | 85 | // if ( ! options.isDev ) { 86 | // grunt.task.run( [ 'uglify' ] ); 87 | // } 88 | 89 | } ); 90 | 91 | grunt.registerTask( 'lint:js', [ 'newer:jshint:common' ] ); 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/autoprefixer.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | return { 3 | dist : { 4 | files : [ { 5 | expand : true, 6 | cwd : options.buildCssDir, 7 | src : [ '*.css' ], 8 | dest : options.buildCssDir 9 | } ] 10 | } 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/clean.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | return { 3 | options : { 4 | force : true 5 | }, 6 | fonts : options.buildFontsDir, 7 | images : options.buildImgDir, 8 | js : options.buildJsDir, 9 | css : options.buildCssDir, 10 | build : options.buildDir 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/copy.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | return { 3 | js : { 4 | expand : true, 5 | cwd : options.devJsDir, 6 | src : '**/*', 7 | dest : options.buildJsDir 8 | }, 9 | vendor : { 10 | expand : true, 11 | cwd : options.devVendorDir, 12 | src : '**/*', 13 | dest : options.buildVendorDir 14 | } 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/cssmin.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | return { 3 | dist : { 4 | files : [ { 5 | expand : true, 6 | cwd : options.buildCssDir, 7 | src : [ '*.css' ], 8 | dest : options.buildCssDir, 9 | ext : '.min.css' 10 | } ] 11 | } 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/growl.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | complete : { 4 | message : 'Task completed', 5 | title : 'Grunt' 6 | //image : __dirname + "/foo.png" 7 | } 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/jshint.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | return { 3 | options : { 4 | jshintrc : './resources/jshint_conf.json' 5 | }, 6 | self : { 7 | files : [ { 8 | expand : true, 9 | cwd : './', 10 | src : [ 11 | 'Gruntfile.js', 12 | 'grunt-configs/**/*.js' 13 | ], 14 | dest : './' 15 | } ] 16 | }, 17 | common : { 18 | files : [ { 19 | expand : true, 20 | cwd : options.devJsDir, 21 | src : [ 22 | 'main.js', 23 | 'app/**/*.js' 24 | ], 25 | dest : options.devJsDir 26 | } ] 27 | } 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/sass.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | var opts = options; 3 | return { 4 | dist : { 5 | options : { 6 | outputStyle : 'nested' 7 | }, 8 | files : [ { 9 | expand : true, 10 | cwd : opts.devCssDir, 11 | src : [ '*.scss', '!_*' ], 12 | dest : opts.buildCssDir, 13 | rename : function ( dest, src ) { 14 | return dest + src.replace( 'scss', 'css' ); 15 | } 16 | } ] 17 | } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/scsslint.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | var opts = options; 3 | return { 4 | dev : { 5 | files : [ { 6 | expand : true, 7 | cwd : opts.devCssDir, 8 | src : [ 9 | '**/*.scss', 10 | '!**/_settings.scss', 11 | '!**/_foundationSettings.scss', 12 | '!**/_foundation.scss' 13 | ], 14 | dest : opts.devCssDir 15 | } ] 16 | }, 17 | options : { 18 | config : './resources/scsslint_conf.yml', 19 | compact : true 20 | } 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/uglify.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | return { 3 | js : { 4 | files : [ { 5 | expand : true, 6 | cwd : options.buildJsDir, 7 | src : [ 8 | '*.js' 9 | ], 10 | ext : '.min.js', 11 | dest : options.buildJsDir 12 | } ] 13 | } 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /source/tasks/grunt-configs/watch.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( grunt, options ) { 2 | var opts = options; 3 | 4 | return { 5 | grunt : { 6 | files : [ 7 | 'Gruntfile.js', 8 | './grunt-configs/**/*.js' 9 | ], 10 | tasks : [ 11 | 'build' 12 | ] 13 | }, 14 | js : { 15 | files : [ 16 | opts.devJsDir + '**/*.js', 17 | ], 18 | tasks : [ 19 | 'clean:js', 20 | 'compile:js', 21 | 'growl:complete' 22 | ], 23 | options : { 24 | spawn : false 25 | } 26 | }, 27 | css : { 28 | files : [ 29 | opts.devCssDir + '**/*.scss' 30 | ], 31 | tasks : [ 32 | 'clean:css', 33 | 'compile:css', 34 | 'growl:complete' 35 | ], 36 | options : { 37 | spawn : false 38 | } 39 | } 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /source/tasks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DashCols", 3 | "description": "Craft plugin for adding custom fields to entry index tables", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/mmikkel/dashcols-craft", 6 | "author": "Mats Mikkel Rummelhoff (http://mmikkel.no/)", 7 | "private": true, 8 | "devDependencies": { 9 | "grunt": "^0.4.5", 10 | "grunt-autoprefixer": "^3.0.3", 11 | "grunt-contrib-clean": "^0.6.0", 12 | "grunt-contrib-copy": "^0.8.2", 13 | "grunt-contrib-cssmin": "^0.14.0", 14 | "grunt-contrib-jshint": "^0.11.3", 15 | "grunt-contrib-uglify": "^0.10.0", 16 | "grunt-contrib-watch": "^0.6.1", 17 | "grunt-growl": "^0.1.5", 18 | "grunt-jscs": "^2.3.0", 19 | "grunt-newer": "^1.1.1", 20 | "grunt-sass": "^1.1.0", 21 | "grunt-scss-lint": "^0.3.8", 22 | "grunt-version-check": "^0.3.1", 23 | "jit-grunt": "^0.9.1", 24 | "load-grunt-config": "^0.19.0", 25 | "node-sass": "^3.4.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/tasks/resources/jshint_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | // http://www.jshint.com/docs/options/ 3 | 4 | // Enforcing options 5 | "boss": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "eqnull": true, 9 | "es3": true, 10 | "expr": true, 11 | "immed": true, 12 | "noarg": true, 13 | "onevar": true, 14 | "quotmark": "single", 15 | "trailing": true, 16 | "undef": false, 17 | "unused": false, 18 | 19 | // Relaxing options 20 | "expr" : true, 21 | "laxbreak" : true, 22 | "maxerr" : 100, 23 | 24 | // Environments 25 | "browser" : true, 26 | "jquery" : true, 27 | 28 | "globals" : { 29 | "_": false, 30 | "Backbone": false, 31 | "jQuery": false, 32 | "wp": false, 33 | "require": false, 34 | "module" : false, 35 | "dk" : false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/tasks/resources/scsslint_conf.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | BorderZero: 3 | enabled: true 4 | 5 | CapitalizationInSelector: 6 | enabled: true 7 | 8 | ColorKeyword: 9 | enabled: true 10 | 11 | Comment: 12 | enabled: false 13 | 14 | DebugStatement: 15 | enabled: true 16 | 17 | DeclarationOrder: 18 | enabled: true 19 | 20 | DuplicateProperty: 21 | enabled: false 22 | 23 | ElsePlacement: 24 | enabled: true 25 | style: same_line # or 'new_line' 26 | 27 | EmptyLineBetweenBlocks: 28 | enabled: true 29 | ignore_single_line_blocks: true 30 | 31 | EmptyRule: 32 | enabled: true 33 | 34 | FinalNewline: 35 | enabled: true 36 | present: true 37 | 38 | HexLength: 39 | enabled: true 40 | style: short # or 'long' 41 | 42 | HexNotation: 43 | enabled: true 44 | style: lowercase # or 'uppercase' 45 | 46 | HexValidation: 47 | enabled: true 48 | 49 | IdWithExtraneousSelector: 50 | enabled: true 51 | 52 | Indentation: 53 | enabled: true 54 | character: tab # or 'space' 55 | width: 1 56 | 57 | LeadingZero: 58 | enabled: true 59 | style: exclude_zero # or 'include_zero' 60 | 61 | MergeableSelector: 62 | enabled: true 63 | force_nesting: true 64 | 65 | NameFormat: 66 | enabled: true 67 | convention: hyphenated_lowercase # or 'BEM', or a regex pattern 68 | 69 | PlaceholderInExtend: 70 | enabled: true 71 | 72 | PropertySortOrder: 73 | enabled: true 74 | ignore_unspecified: false 75 | 76 | PropertySpelling: 77 | enabled: true 78 | extra_properties: [] 79 | 80 | SelectorDepth: 81 | enabled: true 82 | max_depth: 3 83 | 84 | Shorthand: 85 | enabled: true 86 | 87 | SingleLinePerProperty: 88 | enabled: true 89 | allow_single_line_rule_sets: true 90 | 91 | SingleLinePerSelector: 92 | enabled: true 93 | 94 | SpaceAfterComma: 95 | enabled: true 96 | 97 | SpaceAfterPropertyColon: 98 | enabled: true 99 | style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned' 100 | 101 | SpaceAfterPropertyName: 102 | enabled: true 103 | 104 | SpaceBeforeBrace: 105 | enabled: true 106 | allow_single_line_padding: false 107 | 108 | SpaceBetweenParens: 109 | enabled: true 110 | spaces: 0 111 | 112 | StringQuotes: 113 | enabled: true 114 | style: single_quotes # or double_quotes 115 | 116 | TrailingSemicolon: 117 | enabled: true 118 | 119 | UnnecessaryMantissa: 120 | enabled: true 121 | 122 | UnnecessaryParentReference: 123 | enabled: true 124 | 125 | UrlFormat: 126 | enabled: true 127 | 128 | UrlQuotes: 129 | enabled: true 130 | 131 | ZeroUnit: 132 | enabled: true 133 | 134 | Compass::*: 135 | enabled: false --------------------------------------------------------------------------------