├── .gitignore ├── LICENSE ├── mgautocomplete ├── MgAutocompletePlugin.php ├── fieldtypes │ └── MgAutocomplete_TextAutoCompleteFieldType.php ├── resources │ ├── css │ │ └── style.css │ ├── icon-mask.svg │ ├── icon.svg │ └── js │ │ ├── jquery.ui.autocomplete.js │ │ └── jquery.ui.deps.js └── templates │ └── plaintext.html ├── readme.md └── releases.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mildly Geeky, Inc. 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. -------------------------------------------------------------------------------- /mgautocomplete/MgAutocompletePlugin.php: -------------------------------------------------------------------------------- 1 | templates->includeCssResource('mgautocomplete/css/style.css'); 14 | craft()->templates->includeJsResource('mgautocomplete/js/jquery.ui.deps.js'); 15 | craft()->templates->includeJsResource('mgautocomplete/js/jquery.ui.autocomplete.js'); 16 | 17 | return craft()->templates->render('mgautocomplete/plaintext', array( 18 | 'name' => $name, 19 | 'value' => $value, 20 | 'completions' => $this->_getAllCompletions($name, $this->element), 21 | 'settings' => $this->getSettings() 22 | )); 23 | } 24 | 25 | protected function _getAllCompletions($name, $element) 26 | { 27 | $completions = array(); 28 | 29 | $field = craft()->fields->getFieldByHandle($name); 30 | if ($field == null or !$field->hasContentColumn()) { 31 | return $completions; 32 | } 33 | 34 | $contentTable = craft()->content->contentTable; 35 | 36 | $fieldCol = 'field_' . $name; 37 | 38 | $query = craft()->db->createCommand() 39 | ->selectDistinct($fieldCol) 40 | ->from($contentTable) 41 | ->where(array( 42 | 'locale' => $element->getAttribute('locale') 43 | )) 44 | ->andWhere($fieldCol . ' is not null') 45 | ->queryAll(); 46 | 47 | for ($i = 0; $i < count($query); $i++) { 48 | array_push($completions, $query[$i][$fieldCol]); 49 | } 50 | 51 | return $completions; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /mgautocomplete/resources/css/style.css: -------------------------------------------------------------------------------- 1 | .mgautocomplete__ul { 2 | margin-top: -3px; 3 | background: #fff; 4 | border: none; 5 | border-radius: 0 0 2px 2px; 6 | box-shadow: 0 0 2px rgba(51, 170, 255, 0), inset 0 0 0 1px rgba(0, 0, 0, 0.1), inset 0 2px 4px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(51, 170, 255, 0); 7 | } 8 | 9 | .mgautocomplete__ul li { 10 | padding: 6px 7px; 11 | transition: all 100ms cubic-bezier(0.420, 0.000, 0.580, 1.000); /* ease-in-out */ 12 | } 13 | 14 | .mgautocomplete__ul li:last-child { 15 | border-bottom-left-radius: 2px; 16 | border-bottom-right-radius: 2px; 17 | } 18 | 19 | .mgautocomplete__ul li.ui-state-focus { 20 | background: #7dafe8; 21 | color: #fff; 22 | } 23 | 24 | .ui-helper-hidden { 25 | display: none; 26 | } 27 | 28 | .ui-helper-hidden-accessible { 29 | border: 0; 30 | clip: rect(0 0 0 0); 31 | height: 1px; 32 | margin: -1px; 33 | overflow: hidden; 34 | padding: 0; 35 | position: absolute; 36 | width: 1px; 37 | } 38 | 39 | .ui-helper-reset { 40 | margin: 0; 41 | padding: 0; 42 | border: 0; 43 | outline: 0; 44 | line-height: 1.3; 45 | text-decoration: none; 46 | font-size: 100%; 47 | list-style: none; 48 | } 49 | 50 | .ui-helper-clearfix:before, 51 | .ui-helper-clearfix:after { 52 | content: ""; 53 | display: table; 54 | border-collapse: collapse; 55 | } 56 | 57 | .ui-helper-clearfix:after { 58 | clear: both; 59 | } 60 | 61 | .ui-helper-clearfix { 62 | min-height: 0; /* support: IE7 */ 63 | } 64 | 65 | .ui-helper-zfix { 66 | width: 100%; 67 | height: 100%; 68 | top: 0; 69 | left: 0; 70 | position: absolute; 71 | opacity: 0; 72 | filter: Alpha(Opacity=0); /* support: IE8 */ 73 | } 74 | 75 | .ui-front { 76 | z-index: 100; 77 | } 78 | 79 | /* Interaction Cues 80 | ----------------------------------*/ 81 | .ui-state-disabled { 82 | cursor: default !important; 83 | } 84 | 85 | /* Icons 86 | ----------------------------------*/ 87 | 88 | /* states and images */ 89 | .ui-icon { 90 | display: block; 91 | text-indent: -99999px; 92 | overflow: hidden; 93 | background-repeat: no-repeat; 94 | } 95 | 96 | /* Misc visuals 97 | ----------------------------------*/ 98 | 99 | /* Overlays */ 100 | .ui-widget-overlay { 101 | position: fixed; 102 | top: 0; 103 | left: 0; 104 | width: 100%; 105 | height: 100%; 106 | } 107 | 108 | .ui-autocomplete { 109 | position: absolute; 110 | top: 0; 111 | left: 0; 112 | cursor: default; 113 | } 114 | 115 | .ui-menu { 116 | list-style: none; 117 | padding: 0; 118 | margin: 0; 119 | display: block; 120 | outline: none; 121 | } 122 | 123 | .ui-menu .ui-menu { 124 | position: absolute; 125 | } 126 | 127 | .ui-menu .ui-menu-item { 128 | position: relative; 129 | margin: 0; 130 | padding: 3px 1em 3px .4em; 131 | cursor: pointer; 132 | min-height: 0; /* support: IE7 */ 133 | /* support: IE10, see #8844 */ 134 | list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); 135 | } 136 | 137 | .ui-menu .ui-menu-divider { 138 | margin: 5px 0; 139 | height: 0; 140 | font-size: 0; 141 | line-height: 0; 142 | border-width: 1px 0 0 0; 143 | } 144 | 145 | .ui-menu .ui-state-focus, 146 | .ui-menu .ui-state-active { 147 | margin: -1px; 148 | } 149 | 150 | /* icon support */ 151 | .ui-menu-icons { 152 | position: relative; 153 | } 154 | 155 | .ui-menu-icons .ui-menu-item { 156 | padding-left: 2em; 157 | } 158 | 159 | /* left-aligned */ 160 | .ui-menu .ui-icon { 161 | position: absolute; 162 | top: 0; 163 | bottom: 0; 164 | left: .2em; 165 | margin: auto 0; 166 | } 167 | 168 | /* right-aligned */ 169 | .ui-menu .ui-menu-icon { 170 | left: auto; 171 | right: 0; 172 | } -------------------------------------------------------------------------------- /mgautocomplete/resources/icon-mask.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mgautocomplete/resources/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mgautocomplete/resources/js/jquery.ui.autocomplete.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.11.4 - 2015-11-05 2 | * http://jqueryui.com 3 | * Includes: core.js, widget.js, position.js, autocomplete.js, menu.js 4 | * Copyright jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | (function( factory ) { 7 | if ( typeof define === "function" && define.amd ) { 8 | 9 | // AMD. Register as an anonymous module. 10 | define([ "jquery" ], factory ); 11 | } else { 12 | 13 | // Browser globals 14 | factory( jQuery ); 15 | } 16 | }(function( $ ) { 17 | 18 | /*! 19 | * jQuery UI Autocomplete 1.12.1 20 | * http://jqueryui.com 21 | * 22 | * Copyright jQuery Foundation and other contributors 23 | * Released under the MIT license. 24 | * http://jquery.org/license 25 | */ 26 | 27 | //>>label: Autocomplete 28 | //>>group: Widgets 29 | //>>description: Lists suggested words as the user is typing. 30 | //>>docs: http://api.jqueryui.com/autocomplete/ 31 | //>>demos: http://jqueryui.com/autocomplete/ 32 | //>>css.structure: ../../themes/base/core.css 33 | //>>css.structure: ../../themes/base/autocomplete.css 34 | //>>css.theme: ../../themes/base/theme.css 35 | 36 | $.widget( "ui.autocomplete", { 37 | version: "1.12.1", 38 | defaultElement: "", 39 | options: { 40 | appendTo: null, 41 | autoFocus: false, 42 | delay: 300, 43 | minLength: 1, 44 | position: { 45 | my: "left top", 46 | at: "left bottom", 47 | collision: "none" 48 | }, 49 | source: null, 50 | 51 | // Callbacks 52 | change: null, 53 | close: null, 54 | focus: null, 55 | open: null, 56 | response: null, 57 | search: null, 58 | select: null 59 | }, 60 | 61 | requestIndex: 0, 62 | pending: 0, 63 | 64 | _create: function() { 65 | 66 | // Some browsers only repeat keydown events, not keypress events, 67 | // so we use the suppressKeyPress flag to determine if we've already 68 | // handled the keydown event. #7269 69 | // Unfortunately the code for & in keypress is the same as the up arrow, 70 | // so we use the suppressKeyPressRepeat flag to avoid handling keypress 71 | // events when we know the keydown event was used to modify the 72 | // search term. #7799 73 | var suppressKeyPress, suppressKeyPressRepeat, suppressInput, 74 | nodeName = this.element[ 0 ].nodeName.toLowerCase(), 75 | isTextarea = nodeName === "textarea", 76 | isInput = nodeName === "input"; 77 | 78 | // Textareas are always multi-line 79 | // Inputs are always single-line, even if inside a contentEditable element 80 | // IE also treats inputs as contentEditable 81 | // All other element types are determined by whether or not they're contentEditable 82 | this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element ); 83 | 84 | this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; 85 | this.isNewMenu = true; 86 | 87 | this._addClass( "ui-autocomplete-input" ); 88 | this.element.attr( "autocomplete", "off" ); 89 | 90 | 91 | this._on( this.element, { 92 | keydown: function( event ) { 93 | if ( this.element.prop( "readOnly" ) ) { 94 | suppressKeyPress = true; 95 | suppressInput = true; 96 | suppressKeyPressRepeat = true; 97 | return; 98 | } 99 | 100 | suppressKeyPress = false; 101 | suppressInput = false; 102 | suppressKeyPressRepeat = false; 103 | var keyCode = $.ui.keyCode; 104 | switch ( event.keyCode ) { 105 | case keyCode.PAGE_UP: 106 | suppressKeyPress = true; 107 | this._move( "previousPage", event ); 108 | break; 109 | case keyCode.PAGE_DOWN: 110 | suppressKeyPress = true; 111 | this._move( "nextPage", event ); 112 | break; 113 | case keyCode.UP: 114 | suppressKeyPress = true; 115 | this._keyEvent( "previous", event ); 116 | break; 117 | case keyCode.DOWN: 118 | suppressKeyPress = true; 119 | this._keyEvent( "next", event ); 120 | break; 121 | case keyCode.ENTER: 122 | 123 | // when menu is open and has focus 124 | if ( this.menu.active ) { 125 | 126 | // #6055 - Opera still allows the keypress to occur 127 | // which causes forms to submit 128 | suppressKeyPress = true; 129 | event.preventDefault(); 130 | this.menu.select( event ); 131 | } 132 | break; 133 | case keyCode.TAB: 134 | if ( this.menu.active ) { 135 | this.menu.select( event ); 136 | } 137 | break; 138 | case keyCode.ESCAPE: 139 | if ( this.menu.element.is( ":visible" ) ) { 140 | if ( !this.isMultiLine ) { 141 | this._value( this.term ); 142 | } 143 | this.close( event ); 144 | 145 | // Different browsers have different default behavior for escape 146 | // Single press can mean undo or clear 147 | // Double press in IE means clear the whole form 148 | event.preventDefault(); 149 | } 150 | break; 151 | default: 152 | suppressKeyPressRepeat = true; 153 | 154 | // search timeout should be triggered before the input value is changed 155 | this._searchTimeout( event ); 156 | break; 157 | } 158 | }, 159 | keypress: function( event ) { 160 | if ( suppressKeyPress ) { 161 | suppressKeyPress = false; 162 | if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { 163 | event.preventDefault(); 164 | } 165 | return; 166 | } 167 | if ( suppressKeyPressRepeat ) { 168 | return; 169 | } 170 | 171 | // Replicate some key handlers to allow them to repeat in Firefox and Opera 172 | var keyCode = $.ui.keyCode; 173 | switch ( event.keyCode ) { 174 | case keyCode.PAGE_UP: 175 | this._move( "previousPage", event ); 176 | break; 177 | case keyCode.PAGE_DOWN: 178 | this._move( "nextPage", event ); 179 | break; 180 | case keyCode.UP: 181 | this._keyEvent( "previous", event ); 182 | break; 183 | case keyCode.DOWN: 184 | this._keyEvent( "next", event ); 185 | break; 186 | } 187 | }, 188 | input: function( event ) { 189 | if ( suppressInput ) { 190 | suppressInput = false; 191 | event.preventDefault(); 192 | return; 193 | } 194 | this._searchTimeout( event ); 195 | }, 196 | focus: function() { 197 | this.selectedItem = null; 198 | this.previous = this._value(); 199 | }, 200 | blur: function( event ) { 201 | if ( this.cancelBlur ) { 202 | delete this.cancelBlur; 203 | return; 204 | } 205 | 206 | clearTimeout( this.searching ); 207 | this.close( event ); 208 | this._change( event ); 209 | } 210 | } ); 211 | 212 | this._initSource(); 213 | this.menu = $( "