diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f6ab25c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# For more information about the properties used in this file, +# please see the EditorConfig documentation: +# http://editorconfig.org + +# Default for all files +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +# PSR-2 for PHP +[*.php] +indent_style = space + +# The indent size used in the package.json file cannot be changed: +# https://github.com/npm/npm/pull/3180#issuecomment-16336516 +[{*.yml,package.json}] +indent_size = 2 +indent_style = space diff --git a/LICENSE b/LICENSE index 50a4b81..d523ba0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016, Jono Menz +Copyright (c) 2016, Jonathon Menz All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 474f5d6..d4b9a66 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,53 @@ -# silverstripe-text-target-length -Set character length recommendations on SilverStripe text form fields +# Text Target Length for SilverStripe CMS + +![Character limits in action](screenshots/character-count.gif) + +If you see a field marked 'Description' you know roughly what type of content to put in there. But how do you know how much of it to write? A single sentence might do, but maybe a paragraph or more is required? A great content plan should recommend an ideal length for every type of content, so content authors and designers alike can make informed decisions. + +This module extends the `TextField` and `TextareaField` classes in SilverStripe to allow you to set a recommended content length, and set soft upper and lower limits on character count. + +## Requirements + +SilverStripe 3.1+ (3.3 tested) + +## Installation + +### Composer (best practice) + +[Packagist listing](https://packagist.org/packages/jonom/silverstripe-text-target-length) and [installation instructions](http://doc.silverstripe.org/framework/en/trunk/installation/composer#adding-modules-to-your-project) + +### Manually + +I promise it's worth your time to learn how to use Composer. If painless updating isn't your thing though you can download and extract this project, rename the module folder 'text-target-length', place it in your project root and run a dev/build?flush=1. + +## How to use + +With the module installed you can call call `setTargetLength()` on `TextField` and `TextareaField` form fields. + +```php +// Ideal length: 100 characters +// Minimum: 75 (automatically set at 75% of ideal) +// Maximum: 125 (automatically set at 125% of ideal) +$field->setTargetLength(100); + +// Ideal length: 100 characters +// Minimum: 25 +// Maximum: 150 +$field->setTargetLength(100, 25, 150); +``` + +## Want to see more like this? + +I donate a lot of my time to open-source projects, so if you use this module in a commercial project a small donation to keep me motivated would be much appreciated. If you'd like to sponsor additional development on this module I'd love to hear from you. + +[Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z5HEZREZSKA6A) + +## To Do + + - [ ] Extension for HTMLEditorField? + - [ ] Translation + - [ ] Customise hint text through config + +## Maintainer contact + +[jonathonmenz.com](http://jonathonmenz.com) diff --git a/_config.php b/_config.php new file mode 100644 index 0000000..8093b43 --- /dev/null +++ b/_config.php @@ -0,0 +1,6 @@ +owner; + $idealCharCount = (int)$idealCharCount; + if (!$idealCharCount > 0) return $field; + + // Set defaults + if ($minCharCount === null) $minCharCount = round($idealCharCount * .75); + if ($maxCharCount === null) $maxCharCount = round($idealCharCount * 1.25); + + // Validate + if (!($maxCharCount >= $idealCharCount && $idealCharCount >= $minCharCount)) return $field; + + // Activate + $field->addExtraClass('target-length'); + $field->setAttribute('data-target-ideal-length', $idealCharCount); + $field->setAttribute('data-target-min-length', $minCharCount); + $field->setAttribute('data-target-max-length', $maxCharCount); + Requirements::javascript(TEXTTARGETLENGTH_DIR.'/javascript/text-target-length.js'); + Requirements::css(TEXTTARGETLENGTH_DIR.'/css/text-target-length.css'); + + return $field; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5c754e9 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "jonom/silverstripe-text-target-length", + "description": "Set character length recommendations on SilverStripe text form fields", + "type": "silverstripe-module", + "keywords": ["silverstripe", "textfield", "textareafield", "target"], + "license": "BSD-3-Clause", + "authors": [{ + "name": "Jonathon Menz", + "homepage": "http://jonathonmenz.com" + }], + "require": { + "silverstripe/cms": "^3.1" + }, + "extra": { + "installer-name": "text-target-length" + } +} diff --git a/css/text-target-length.css b/css/text-target-length.css new file mode 100644 index 0000000..9d01d25 --- /dev/null +++ b/css/text-target-length.css @@ -0,0 +1,20 @@ +.target-length p.target-length-count { + color: #8796a3; + margin: 0 auto; + padding: .25em 0; +} +.target-length p.target-length-count i, +.target-length p.target-length-count b { + color: #00a651; +} +.target-length p.target-length-count b { + font-weight: bold; +} +.target-length p.target-length-count.over i, +.target-length p.target-length-count.over b { + color: #e3080a; +} +.target-length p.target-length-count.under i, +.target-length p.target-length-count.under b { + color: #e39d08; +} diff --git a/javascript/text-target-length.js b/javascript/text-target-length.js new file mode 100755 index 0000000..ee8b66c --- /dev/null +++ b/javascript/text-target-length.js @@ -0,0 +1,58 @@ +(function($) { + $.entwine('ss.targetlength', function($){ + + $('div.field.target-length input').entwine({ + + updateCount: function() { + var field = $(this); + var countEl = field.next('p.target-length-count'); + if (!countEl) return; + var charCount = field.val().length; + if (field.data('previousCount') === charCount) return; + var ideal = field.data('targetIdealLength'); + var min = field.data('targetMinLength'); + var max = field.data('targetMaxLength'); + var targetFulfilled = Math.round((charCount / ideal)*20)*5; //5% increments + var remark = 'Great!'; + var remarkClass = 'good'; + if ((charCount >= min && charCount < ((min + ideal) / 2)) || (charCount <= max && charCount > ((max + ideal) / 2))) { + remark = 'Okay'; + } else if (charCount < min) { + remark = 'Keep going!'; + remarkClass = 'under'; + if (charCount === 0) remark = ''; + } else if (charCount > max) { + remark = 'Too long' + remarkClass = 'over'; + } + countEl.attr('class', remarkClass + ' target-length-count'); + countEl.html('Length target: ' + targetFulfilled + '% ' + remark + ''); + field.data('previousCount', charCount); + }, + onadd: function() { + // Insert extra markup + var field = $(this); + field.parent().append('

'); + this.updateCount(); + }, + onpropertychange: function() { + this.updateCount(); + }, + onchange: function() { + this.updateCount(); + }, + onclick: function() { + this.updateCount(); + }, + onkeyup: function() { + this.updateCount(); + }, + oninput: function() { + this.updateCount(); + }, + onpaste: function() { + this.updateCount(); + } + }); + }); +}(jQuery)); diff --git a/screenshots/character-count.gif b/screenshots/character-count.gif new file mode 100644 index 0000000..82aa68a Binary files /dev/null and b/screenshots/character-count.gif differ