├── .editorconfig ├── .github └── workflows │ └── comment-issue.yml ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .versions ├── LICENSE ├── README.md ├── autoform-select2.html ├── autoform-select2.js └── package.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | [*.js] 12 | max_line_length = 80 13 | indent_brace_style = 1TBS 14 | spaces_around_operators = true 15 | quote_type = auto -------------------------------------------------------------------------------- /.github/workflows/comment-issue.yml: -------------------------------------------------------------------------------- 1 | name: Add immediate comment on new issues 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | createComment: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Create Comment 12 | uses: peter-evans/create-or-update-comment@v1.4.2 13 | with: 14 | issue-number: ${{ github.event.issue.number }} 15 | body: | 16 | Thank you for submitting this issue! 17 | 18 | We, the Members of Meteor Community Packages take every issue seriously. 19 | Our goal is to provide long-term lifecycles for packages and keep up 20 | with the newest changes in Meteor and the overall NodeJs/JavaScript ecosystem. 21 | 22 | However, we contribute to these packages mostly in our free time. 23 | Therefore, we can't guarantee your issues to be solved within certain time. 24 | 25 | If you think this issue is trivial to solve, don't hesitate to submit 26 | a pull request, too! We will accompany you in the process with reviews and hints 27 | on how to get development set up. 28 | 29 | Please also consider sponsoring the maintainers of the package. 30 | If you don't know who is currently maintaining this package, just leave a comment 31 | and we'll let you know 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build* 3 | .npm* 4 | .meteor* 5 | smart.lock 6 | nbproject* 7 | /packages/ 8 | .idea/ 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | //.jshintrc 2 | { 3 | // JSHint Meteor Configuration File 4 | // Match the Meteor Style Guide 5 | // 6 | // By @raix with contributions from @aldeed and @awatson1978 7 | // Source https://github.com/raix/Meteor-jshintrc 8 | // 9 | // See http://jshint.com/docs/ for more details 10 | 11 | "maxerr" : 50, // {int} Maximum error before stopping 12 | 13 | // Enforcing 14 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 15 | "camelcase" : true, // true: Identifiers must be in camelCase 16 | "curly" : true, // true: Require {} for every new block or scope 17 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 18 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 19 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 20 | "indent" : 2, // {int} Number of spaces to use for indentation 21 | "latedef" : false, // true: Require variables/functions to be defined before being used 22 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 23 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 24 | "noempty" : true, // true: Prohibit use of empty blocks 25 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 26 | "plusplus" : false, // true: Prohibit use of `++` & `--` 27 | "quotmark" : false, // Quotation mark consistency: 28 | // false : do nothing (default) 29 | // true : ensure whatever is used is consistent 30 | // "single" : require single quotes 31 | // "double" : require double quotes 32 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 33 | "unused" : true, // true: Require all defined variables be used 34 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode 35 | "trailing" : true, // true: Prohibit trailing whitespaces 36 | "maxparams" : false, // {int} Max number of formal params allowed per function 37 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 38 | "maxstatements" : false, // {int} Max number statements per function 39 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 40 | "maxlen" : 80, // {int} Max number of characters per line 41 | 42 | // Relaxing 43 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 44 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 45 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 46 | "eqnull" : false, // true: Tolerate use of `== null` 47 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 48 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 49 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 50 | // (ex: `for each`, multiple try/catch, function expression…) 51 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 52 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 53 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 54 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 55 | "iterator" : false, // true: Tolerate using the `__iterator__` property 56 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 57 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 58 | "laxcomma" : false, // true: Tolerate comma-first style coding 59 | "loopfunc" : false, // true: Tolerate functions being defined in loops 60 | "multistr" : false, // true: Tolerate multi-line strings 61 | "proto" : false, // true: Tolerate using the `__proto__` property 62 | "scripturl" : false, // true: Tolerate script-targeted URLs 63 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 64 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 65 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 66 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 67 | "validthis" : false, // true: Tolerate using this in a non-constructor function 68 | 69 | // Environments 70 | "browser" : true, // Web Browser (window, document, etc) 71 | "couch" : false, // CouchDB 72 | "devel" : true, // Development/debugging (alert, confirm, etc) 73 | "dojo" : false, // Dojo Toolkit 74 | "jquery" : false, // jQuery 75 | "mootools" : false, // MooTools 76 | "node" : false, // Node.js 77 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 78 | "prototypejs" : false, // Prototype and Scriptaculous 79 | "rhino" : false, // Rhino 80 | "worker" : false, // Web Workers 81 | "wsh" : false, // Windows Scripting Host 82 | "yui" : false, // Yahoo User Interface 83 | //"meteor" : false, // Meteor.js 84 | 85 | // Legacy 86 | "nomen" : false, // true: Prohibit dangling `_` in variables 87 | "onevar" : false, // true: Allow only one `var` statement per function 88 | "passfail" : false, // true: Stop on first error 89 | "white" : false, // true: Check against strict whitespace and indentation rules 90 | 91 | // Custom Globals 92 | "predef" : [ 93 | "Meteor", 94 | "Accounts", 95 | "Session", 96 | "Template", 97 | "check", 98 | "Match", 99 | "Deps", 100 | "EJSON", 101 | "Email", 102 | "Package", 103 | "Tinytest", 104 | "Npm", 105 | "Assets", 106 | "Packages", 107 | "process", 108 | "LocalCollection", 109 | "_", 110 | "Random", 111 | "HTTP" 112 | ] // additional predefined global variables 113 | } 114 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - "curl -L http://git.io/ejPSng | /bin/sh" -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | aldeed:autoform@6.0.0 2 | aldeed:autoform-select2@3.0.1 3 | aldeed:template-extension@4.0.0 4 | babel-compiler@6.19.4 5 | babel-runtime@1.0.1 6 | base64@1.0.10 7 | blaze@2.3.2 8 | blaze-tools@1.0.10 9 | boilerplate-generator@1.1.1 10 | caching-compiler@1.1.9 11 | caching-html-compiler@1.0.6 12 | callback-hook@1.0.10 13 | check@1.2.5 14 | ddp@1.3.0 15 | ddp-client@2.0.0 16 | ddp-common@1.2.9 17 | ddp-server@2.0.0 18 | deps@1.0.12 19 | diff-sequence@1.0.7 20 | ecmascript@0.8.1 21 | ecmascript-runtime@0.4.1 22 | ecmascript-runtime-client@0.4.3 23 | ecmascript-runtime-server@0.4.1 24 | ejson@1.0.13 25 | geojson-utils@1.0.10 26 | html-tools@1.0.11 27 | htmljs@1.0.11 28 | id-map@1.0.9 29 | jquery@1.11.10 30 | livedata@1.0.18 31 | logging@1.1.17 32 | meteor@1.7.0 33 | minimongo@1.2.1 34 | modules@0.9.2 35 | modules-runtime@0.8.0 36 | momentjs:moment@2.10.6 37 | mongo-id@1.0.6 38 | observe-sequence@1.0.16 39 | ordered-dict@1.0.9 40 | promise@0.8.9 41 | random@1.0.10 42 | reactive-dict@1.1.9 43 | reactive-var@1.0.11 44 | retry@1.0.9 45 | routepolicy@1.0.12 46 | spacebars@1.0.15 47 | spacebars-compiler@1.1.2 48 | templating@1.1.7 49 | templating-tools@1.1.1 50 | tracker@1.1.3 51 | ui@1.0.13 52 | underscore@1.0.10 53 | webapp@1.3.17 54 | webapp-hashing@1.0.9 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 Eric Dobbertin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | aldeed:autoform-select2 2 | ========================= 3 | 4 | An add-on Meteor package for [aldeed:autoform](https://github.com/aldeed/meteor-autoform). Provides a single custom input type, "select2", which renders an input using the [select2](https://select2.github.io/) plugin. 5 | 6 | ## Prerequisites 7 | 8 | ### Select2 Library 9 | 10 | You must use select2 4.0+. 11 | 12 | Option 1: 13 | 14 | Add this to ``: 15 | 16 | ```html 17 | 18 | 19 | ``` 20 | 21 | Option 2: 22 | 23 | Install the NPM package (and its jQuery dependency): 24 | 25 | ```js 26 | $ meteor npm i --save jquery select2 27 | ``` 28 | 29 | Then in your `client/main.js`: 30 | 31 | ```js 32 | import 'select2'; 33 | import 'select2/dist/css/select2.css'; 34 | ``` 35 | 36 | Option 3: 37 | 38 | Get the files from GitHub and add them directly in your app /client/lib folder. 39 | 40 | ### AutoForm 41 | 42 | In a Meteor app directory, enter: 43 | 44 | ```bash 45 | $ meteor add aldeed:autoform 46 | ``` 47 | 48 | ### Select2 Bootstrap CSS (optional) 49 | 50 | If using with Bootstrap, you can add the theme. 51 | 52 | ```bash 53 | $ meteor npm i --save select2-bootstrap-theme 54 | ``` 55 | 56 | And add an additional import AFTER the other two in your `client/main.js`: 57 | 58 | ```js 59 | import 'select2'; 60 | import 'select2/dist/css/select2.css'; 61 | import 'select2-bootstrap-theme/dist/select2-bootstrap.css'; 62 | ``` 63 | 64 | ## Installation 65 | 66 | In a Meteor app directory, enter: 67 | 68 | ```bash 69 | $ meteor add aldeed:autoform-select2 70 | ``` 71 | 72 | ## Usage 73 | 74 | Specify "select2" for the `type` attribute of any input. This can be done in a number of ways: 75 | 76 | In the schema, which will then work with a `quickForm` or `afQuickFields`: 77 | 78 | ```js 79 | { 80 | tags: { 81 | type: Array, 82 | autoform: { 83 | type: 'select2', 84 | afFieldInput: { 85 | multiple: true 86 | } 87 | } 88 | }, 89 | 'tags.$': String 90 | } 91 | ``` 92 | 93 | Or on the `afFieldInput` component or any component that passes along attributes to `afFieldInput`: 94 | 95 | ```html 96 | {{> afQuickField name="tags" type="select2" multiple=true}} 97 | 98 | {{> afFormGroup name="tags" type="select2" multiple=true}} 99 | 100 | {{> afFieldInput name="tags" type="select2" multiple=true}} 101 | ``` 102 | 103 | ## Setting Select2 Options 104 | 105 | To provide select2 options, set a `select2Options` attribute equal to a helper function that returns the options object. Most of the `data-` attributes that the plugin recognizes should also work. 106 | 107 | Example: 108 | 109 | ```html 110 | {{> afFieldInput name="tags" type="select2" multiple=true select2Options=s2Opts}} 111 | ``` 112 | 113 | ```js 114 | Template.example.helpers({ 115 | s2Opts() { 116 | return { placeholder: 'foo', tags: true }; 117 | }, 118 | }); 119 | ``` 120 | 121 | ## Demo 122 | 123 | [Live](http://autoform.meteor.com/types) 124 | 125 | ## Contributing 126 | 127 | Anyone is welcome to contribute. Fork, make your changes, and then submit a pull request. 128 | -------------------------------------------------------------------------------- /autoform-select2.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /autoform-select2.js: -------------------------------------------------------------------------------- 1 | /* global AutoForm, _, $, Template */ 2 | 3 | AutoForm.addInputType('select2', { 4 | template: 'afSelect2', 5 | valueConverters: { 6 | stringArray: function (val) { 7 | if (_.isArray(val)) { 8 | return _.map(val, function (item) { 9 | return $.trim(item); 10 | }); 11 | } 12 | return val; 13 | }, 14 | number: AutoForm.Utility.stringToNumber, 15 | numberArray: function (val) { 16 | if (_.isArray(val)) { 17 | return _.map(val, function (item) { 18 | item = $.trim(item); 19 | return AutoForm.Utility.stringToNumber(item); 20 | }); 21 | } 22 | return val; 23 | }, 24 | boolean: AutoForm.Utility.stringToBool, 25 | booleanArray: function (val) { 26 | if (_.isArray(val)) { 27 | return _.map(val, function (item) { 28 | item = $.trim(item); 29 | return AutoForm.Utility.stringToBool(item); 30 | }); 31 | } 32 | return val; 33 | }, 34 | date: AutoForm.Utility.stringToDate, 35 | dateArray: function (val) { 36 | if (_.isArray(val)) { 37 | return _.map(val, function (item) { 38 | item = $.trim(item); 39 | return AutoForm.Utility.stringToDate(item); 40 | }); 41 | } 42 | return val; 43 | } 44 | }, 45 | contextAdjust: function (context) { 46 | var itemAtts = _.omit(context.atts, 'firstOption'); 47 | 48 | // NOTE: We don't add firstOption to select2 input because 49 | // it doesn't make sense with the way select2 works. 50 | 51 | // build items list 52 | context.items = []; 53 | 54 | // When single-select and placeholder is passed, 55 | // the first option should be an empty option. 56 | var multiple = itemAtts.multiple; 57 | var select2Options = itemAtts.select2Options || {}; 58 | 59 | if (!multiple && select2Options.placeholder) { 60 | context.items.push(''); 61 | } 62 | 63 | // Check if option is selected 64 | var isSelected = function(conVal, optVal) { 65 | return _.isArray(conVal) ? _.contains(conVal, optVal) : optVal === conVal; 66 | }; 67 | 68 | // Add all defined options 69 | _.each(context.selectOptions, function(opt) { 70 | if (opt.optgroup) { 71 | var subItems = _.map(opt.options, function(subOpt) { 72 | return { 73 | name: context.name, 74 | label: subOpt.label, 75 | value: subOpt.value, 76 | // _id must be included because it is a special property that 77 | // #each uses to track unique list items when adding and removing them 78 | // See https://github.com/meteor/meteor/issues/2174 79 | _id: subOpt.value, 80 | selected: isSelected(context.value, subOpt.value), 81 | atts: itemAtts 82 | }; 83 | }); 84 | context.items.push({ 85 | optgroup: opt.optgroup, 86 | items: subItems 87 | }); 88 | } else { 89 | context.items.push({ 90 | name: context.name, 91 | label: opt.label, 92 | value: opt.value, 93 | // _id must be included because it is a special property that 94 | // #each uses to track unique list items when adding and removing them 95 | // See https://github.com/meteor/meteor/issues/2174 96 | _id: opt.value, 97 | selected: isSelected(context.value, opt.value), 98 | atts: itemAtts 99 | }); 100 | } 101 | }); 102 | 103 | return context; 104 | } 105 | }); 106 | 107 | Template.afSelect2.helpers({ 108 | atts: function addFormControlAtts() { 109 | return _.omit(this.atts, 'select2Options'); 110 | } 111 | }); 112 | 113 | Template.afSelect2.events({ 114 | 'select2:select select': function (event, template) { 115 | // When select2 selection changes, we update the `selected` attr 116 | // on the real select element. This persists better when the DOM 117 | // changes, allowing us to retain selection properly by using this 118 | // in the template autorun. 119 | // Fixes #18 120 | var val = template.$('select').val(); 121 | if (!_.isArray(val)) { val = [val]; } 122 | template.$('select option').each(function () { 123 | var $this = $(this); 124 | var selected = val.indexOf($this.attr('value')) !== -1; 125 | $this.prop('selected', selected).attr('selected', selected); 126 | }); 127 | } 128 | }); 129 | 130 | Template.afSelect2.onRendered(function () { 131 | var template = this; 132 | var $s = template.$('select'); 133 | 134 | // instanciate select2 135 | $s.select2(template.data.atts.select2Options || {}); 136 | 137 | template.autorun(function () { 138 | var data = Template.currentData(); 139 | 140 | var values = []; 141 | _.each(data.items, function (item) { 142 | if (_.has(item, 'items')) { 143 | _.each(item.items, function (subItem) { 144 | if (subItem.selected) { 145 | values.push(subItem.value); 146 | } 147 | }); 148 | } else { 149 | if (item.selected) { 150 | values.push(item.value); 151 | } 152 | } 153 | }); 154 | 155 | var $selects; 156 | if (values.length === 0) { 157 | $selects = template.$('select option'); 158 | } else { 159 | // Include any that were previously added as new tags 160 | $selects = template.$('select option[data-select2-tag]'); 161 | } 162 | 163 | $selects.each(function () { 164 | var $this = $(this); 165 | if ($this.attr('selected')) { 166 | values.push($this.attr('value')); 167 | } 168 | }); 169 | 170 | var currentValues = $s.val(); 171 | if ((!currentValues && values.length > 0) || 172 | (currentValues && currentValues.toString() !== values.toString())) { 173 | // select2 requires that we trigger change event 174 | // for it to realize it needs to update the select2 list. 175 | // We do it only if values have actually changed, 176 | // which should help prevent autosave infinite looping. 177 | $s.val(values).trigger('change'); 178 | } 179 | }); 180 | }); 181 | 182 | Template.afSelect2.onDestroyed(function () { 183 | try { 184 | if (this.view && this.view._domrange && this.$('select').data('select2')) { 185 | this.$('select').select2('destroy'); 186 | } 187 | } catch (error) {} 188 | }); 189 | 190 | /* 191 | * BOOTSTRAP THEME 192 | */ 193 | 194 | Template.afSelect2.copyAs('afSelect2_bootstrap3'); 195 | 196 | // The only difference is that we need to add "form-control" class 197 | Template.afSelect2_bootstrap3.helpers({ 198 | atts: function addFormControlAtts() { 199 | var atts = _.omit(this.atts, 'select2Options'); 200 | // Add bootstrap class 201 | atts = AutoForm.Utility.addClass(atts, 'form-control'); 202 | return atts; 203 | } 204 | }); 205 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'aldeed:autoform-select2', 3 | summary: 'Custom select2 input type for AutoForm', 4 | version: '3.0.1', 5 | git: 'https://github.com/aldeed/meteor-autoform-select2.git' 6 | }); 7 | 8 | Package.onUse(function(api) { 9 | api.use('underscore@1.0.0'); 10 | api.use('templating@1.0.0'); 11 | api.use('blaze@2.0.0'); 12 | api.use('aldeed:template-extension@4.0.0'); 13 | api.use('aldeed:autoform@6.0.0'); 14 | api.addFiles([ 15 | 'autoform-select2.html', 16 | 'autoform-select2.js' 17 | ], 'client'); 18 | }); 19 | --------------------------------------------------------------------------------