populate-me 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 04d5994053986cb49a277559ebabe1caf7ca5e6a
4
- data.tar.gz: 16f1d48e316b78fd11db94d03d70e6c78df8fb30
3
+ metadata.gz: 7ff6598db649ef34bc8d9d944eba83353d4efd8c
4
+ data.tar.gz: 6a6811373d3f8e17bad20d968888ca628d4561e3
5
5
  SHA512:
6
- metadata.gz: 2672c99f4bad24d2c1b30eae320001006e022455fdb0a9d3b638b916c8702a4f9ee45dc3e477d5aefca121ed493de31129e7faec245111df758495b30a8ff289
7
- data.tar.gz: 2e93eebd1c02c7a52183f9d257cd888bd9649349ef546295a0a8a9750b2f522278903858d8f6a268338f581d2e68ef9f6679b6c7e20c9682757abe07bcf1a6aa
6
+ metadata.gz: 04e3e69f706b562e5e5d193a279851d7fa6836b45eb5f80da2dc3c560011d02f37eece21dedba802f4cb1be3c7dcf62f8e2092d2824be61a26767bbe24929eb3
7
+ data.tar.gz: 5365b1d443fbaadd2b3b44caf932a187546dc174f858c9a53bcae53ff200c7cda44445aca8913a8d8620ca0c12fa6ddcdc8bea029d01db55f792ab254a5bd74c
data/example/config.ru CHANGED
@@ -59,7 +59,13 @@ end
59
59
  class Article < PopulateMe::Document
60
60
  field :title
61
61
  field :content, type: :text
62
+ field :yes_or_no, type: :select, select_options: [:yes,:no]
63
+ field :tags, type: :select, multiple: true, select_options: ['art','sport','science']
62
64
  position_field
65
+
66
+ after :save do
67
+ puts self.inspect
68
+ end
63
69
  end
64
70
 
65
71
  # Admin ##########
@@ -0,0 +1,63 @@
1
+ .asmContainer {
2
+ /* container that surrounds entire asmSelect widget */
3
+ }
4
+
5
+ .asmSelect {
6
+ /* the newly created regular 'select' */
7
+ display: inline;
8
+ }
9
+
10
+ .asmOptionDisabled {
11
+ /* disabled options in new select */
12
+ color: #999;
13
+ }
14
+
15
+ .asmHighlight {
16
+ /* the highlight span */
17
+ padding: 0;
18
+ margin: 0 0 0 1em;
19
+ }
20
+
21
+ .asmList {
22
+ /* html list that contains selected items */
23
+ margin: 0.25em 0 1em 0;
24
+ position: relative;
25
+ display: block;
26
+ padding-left: 0;
27
+ list-style: none;
28
+ }
29
+
30
+ .asmListItem {
31
+ /* li item from the html list above */
32
+ position: relative;
33
+ margin-left: 0;
34
+ padding-left: 0;
35
+ list-style: none;
36
+ background: #ddd;
37
+ border: 1px solid #bbb;
38
+ width: 100%;
39
+ margin: 0 0 -1px 0;
40
+ line-height: 1em;
41
+ }
42
+
43
+ .asmListItem:hover {
44
+ background-color: #e5e5e5;
45
+ }
46
+
47
+ .asmListItemLabel {
48
+ /* this is a span that surrounds the text in the item, except for the remove link */
49
+ padding: 5px;
50
+ display: block;
51
+ }
52
+
53
+ .asmListSortable .asmListItemLabel {
54
+ cursor: move;
55
+ }
56
+
57
+ .asmListItemRemove {
58
+ /* the remove link in each list item */
59
+ position: absolute;
60
+ right: 0;
61
+ top: 0;
62
+ padding: 5px;
63
+ }
@@ -0,0 +1,6 @@
1
+ /*! jQuery UI - v1.12.1 - 2018-02-23
2
+ * http://jqueryui.com
3
+ * Includes: sortable.css
4
+ * Copyright jQuery Foundation and other contributors; Licensed MIT */
5
+
6
+ .ui-sortable-handle{-ms-touch-action:none;touch-action:none}
@@ -66,7 +66,7 @@ button { cursor: pointer; }
66
66
  margin-bottom: 1em;
67
67
  }
68
68
 
69
- button.admin-delete, button.admin-delete-nested, .handle {
69
+ button.admin-delete, button.admin-delete-nested, .handle-button {
70
70
  display: inline-block; vertical-align: middle;
71
71
  font-size: 1em; line-height: 1em;
72
72
  width: auto; height: auto;
@@ -78,14 +78,19 @@ button.admin-delete, button.admin-delete-nested {
78
78
  color: #dc322f;
79
79
  background-color: transparent;
80
80
  }
81
- button.admin-delete:focus, button.admin-delete-nested:focus, .handle:focus {
81
+ button.admin-delete:focus, button.admin-delete-nested:focus, .handle-button:focus {
82
82
  outline: 0;
83
83
  }
84
84
  .handle {
85
85
  cursor: move;
86
- color: #b58900;
86
+ cursor: -moz-grab;
87
+ cursor: -webkit-grab;
88
+ cursor: grab;
87
89
  }
88
- .handle:hover {
90
+ .handle:active {
91
+ cursor: -moz-grabbing;
92
+ cursor: -webkit-grabbing;
93
+ cursor: grabbing;
89
94
  }
90
95
 
91
96
  [type=submit] {
@@ -161,6 +166,27 @@ fieldset {
161
166
  color: #dc322f;
162
167
  }
163
168
 
169
+ /* asmSelect */
170
+
171
+ .asmListItem {
172
+ background-color: #ffffff;
173
+ border: 1px solid #fdf6e3;
174
+ }
175
+ .asmListItem:hover { background-color: #eee8d5; }
176
+ .asmListItemLabel { padding-right: 2em; }
177
+ .asmListSortable .asmListItemLabel {
178
+ cursor: move;
179
+ cursor: -moz-grab;
180
+ cursor: -webkit-grab;
181
+ cursor: grab;
182
+ }
183
+ .asmListSortable .asmListItemLabel:active {
184
+ cursor: -moz-grabbing;
185
+ cursor: -webkit-grabbing;
186
+ cursor: grabbing;
187
+ }
188
+ .asmListItemRemove, .asmListItemRemove:hover { color: #dc322f; }
189
+
164
190
 
165
191
  /\
166
192
  / *\
@@ -0,0 +1,412 @@
1
+ /*
2
+ * Alternate Select Multiple (asmSelect) 1.0.4a beta - jQuery Plugin
3
+ * http://www.ryancramer.com/projects/asmselect/
4
+ *
5
+ * Copyright (c) 2009 by Ryan Cramer - http://www.ryancramer.com
6
+ *
7
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
8
+ * and GPL (GPL-LICENSE.txt) licenses.
9
+ *
10
+ */
11
+
12
+ // The references to $.browser where commented out because it
13
+ // breaks with newer version of jQuery.
14
+
15
+ (function($) {
16
+
17
+ $.fn.asmSelect = function(customOptions) {
18
+
19
+ var options = {
20
+
21
+ listType: 'ol', // Ordered list 'ol', or unordered list 'ul'
22
+ sortable: false, // Should the list be sortable?
23
+ highlight: false, // Use the highlight feature?
24
+ animate: false, // Animate the the adding/removing of items in the list?
25
+ addItemTarget: 'bottom', // Where to place new selected items in list: top or bottom
26
+ hideWhenAdded: false, // Hide the option when added to the list? works only in FF
27
+ debugMode: false, // Debug mode keeps original select visible
28
+
29
+ removeLabel: 'remove', // Text used in the "remove" link
30
+ highlightAddedLabel: 'Added: ', // Text that precedes highlight of added item
31
+ highlightRemovedLabel: 'Removed: ', // Text that precedes highlight of removed item
32
+
33
+ containerClass: 'asmContainer', // Class for container that wraps this widget
34
+ selectClass: 'asmSelect', // Class for the newly created <select>
35
+ optionDisabledClass: 'asmOptionDisabled', // Class for items that are already selected / disabled
36
+ listClass: 'asmList', // Class for the list ($ol)
37
+ listSortableClass: 'asmListSortable', // Another class given to the list when it is sortable
38
+ listItemClass: 'asmListItem', // Class for the <li> list items
39
+ listItemLabelClass: 'asmListItemLabel', // Class for the label text that appears in list items
40
+ removeClass: 'asmListItemRemove', // Class given to the "remove" link
41
+ highlightClass: 'asmHighlight' // Class given to the highlight <span>
42
+
43
+ };
44
+
45
+ $.extend(options, customOptions);
46
+
47
+ return this.each(function(index) {
48
+
49
+ var $original = $(this); // the original select multiple
50
+ var $container; // a container that is wrapped around our widget
51
+ var $select; // the new select we have created
52
+ var $ol; // the list that we are manipulating
53
+ var buildingSelect = false; // is the new select being constructed right now?
54
+ var ieClick = false; // in IE, has a click event occurred? ignore if not
55
+ var ignoreOriginalChangeEvent = false; // originalChangeEvent bypassed when this is true
56
+
57
+ function init() {
58
+
59
+ // initialize the alternate select multiple
60
+
61
+ // this loop ensures uniqueness, in case of existing asmSelects placed by ajax (1.0.3)
62
+ while($("#" + options.containerClass + index).size() > 0) index++;
63
+
64
+ $select = $("<select></select>")
65
+ .addClass(options.selectClass)
66
+ .attr('name', options.selectClass + index)
67
+ .attr('id', options.selectClass + index);
68
+
69
+ $selectRemoved = $("<select></select>");
70
+
71
+ $ol = $("<" + options.listType + "></" + options.listType + ">")
72
+ .addClass(options.listClass)
73
+ .attr('id', options.listClass + index);
74
+
75
+ $container = $("<div></div>")
76
+ .addClass(options.containerClass)
77
+ .attr('id', options.containerClass + index);
78
+
79
+ buildSelect();
80
+
81
+ $select.change(selectChangeEvent)
82
+ .click(selectClickEvent);
83
+
84
+ $original.change(originalChangeEvent)
85
+ .wrap($container).before($select).before($ol);
86
+
87
+ if(options.sortable) makeSortable();
88
+
89
+ // if($.browser.msie && $.browser.version < 8) $ol.css('display', 'inline-block'); // Thanks Matthew Hutton
90
+ }
91
+
92
+ function makeSortable() {
93
+
94
+ // make any items in the selected list sortable
95
+ // requires jQuery UI sortables, draggables, droppables
96
+
97
+ $ol.sortable({
98
+ items: 'li.' + options.listItemClass,
99
+ handle: '.' + options.listItemLabelClass,
100
+ axis: 'y',
101
+ update: function(e, data) {
102
+
103
+ var updatedOptionId;
104
+
105
+ $(this).children("li").each(function(n) {
106
+
107
+ $option = $('#' + $(this).attr('rel'));
108
+
109
+ if($(this).is(".ui-sortable-helper")) {
110
+ updatedOptionId = $option.attr('id');
111
+ return;
112
+ }
113
+
114
+ $original.append($option);
115
+ });
116
+
117
+ if(updatedOptionId) triggerOriginalChange(updatedOptionId, 'sort');
118
+ }
119
+
120
+ }).addClass(options.listSortableClass);
121
+ }
122
+
123
+ function selectChangeEvent(e) {
124
+
125
+ // an item has been selected on the regular select we created
126
+ // check to make sure it's not an IE screwup, and add it to the list
127
+
128
+ // if($.browser.msie && $.browser.version < 7 && !ieClick) return;
129
+ var id = $(this).children("option:selected").slice(0,1).attr('rel');
130
+ addListItem(id);
131
+ ieClick = false;
132
+ triggerOriginalChange(id, 'add'); // for use by user-defined callbacks
133
+ }
134
+
135
+ function selectClickEvent() {
136
+
137
+ // IE6 lets you scroll around in a select without it being pulled down
138
+ // making sure a click preceded the change() event reduces the chance
139
+ // if unintended items being added. there may be a better solution?
140
+
141
+ ieClick = true;
142
+ }
143
+
144
+ function originalChangeEvent(e) {
145
+
146
+ // select or option change event manually triggered
147
+ // on the original <select multiple>, so rebuild ours
148
+
149
+ if(ignoreOriginalChangeEvent) {
150
+ ignoreOriginalChangeEvent = false;
151
+ return;
152
+ }
153
+
154
+ $select.empty();
155
+ $ol.empty();
156
+ buildSelect();
157
+
158
+ // opera has an issue where it needs a force redraw, otherwise
159
+ // the items won't appear until something else forces a redraw
160
+ // if($.browser.opera) $ol.hide().fadeIn("fast");
161
+ }
162
+
163
+ function buildSelect() {
164
+
165
+ // build or rebuild the new select that the user
166
+ // will select items from
167
+
168
+ buildingSelect = true;
169
+
170
+ // add a first option to be the home option / default selectLabel
171
+ $select.prepend("<option>" + $original.attr('title') + "</option>");
172
+
173
+ $original.children("option").each(function(n) {
174
+
175
+ var $t = $(this);
176
+ var id;
177
+
178
+ if(!$t.attr('id')) $t.attr('id', 'asm' + index + 'option' + n);
179
+ id = $t.attr('id');
180
+
181
+ if($t.is(":selected")) {
182
+ addListItem(id);
183
+ addSelectOption(id, true);
184
+ } else {
185
+ addSelectOption(id);
186
+ }
187
+ });
188
+
189
+ if(!options.debugMode) $original.hide(); // IE6 requires this on every buildSelect()
190
+ selectFirstItem();
191
+ buildingSelect = false;
192
+ }
193
+
194
+ function addSelectOption(optionId, disabled) {
195
+
196
+ // add an <option> to the <select>
197
+ // used only by buildSelect()
198
+
199
+ if(disabled == undefined) var disabled = false;
200
+
201
+ var $O = $('#' + optionId);
202
+ var $option = $("<option>" + $O.text() + "</option>")
203
+ .val($O.val())
204
+ .attr('rel', optionId);
205
+
206
+ if(disabled) disableSelectOption($option);
207
+
208
+ $select.append($option);
209
+ }
210
+
211
+ function selectFirstItem() {
212
+
213
+ // select the firm item from the regular select that we created
214
+
215
+ $select.children(":eq(0)").attr("selected", true);
216
+ }
217
+
218
+ function disableSelectOption($option) {
219
+
220
+ // make an option disabled, indicating that it's already been selected
221
+ // because safari is the only browser that makes disabled items look 'disabled'
222
+ // we apply a class that reproduces the disabled look in other browsers
223
+
224
+ $option.addClass(options.optionDisabledClass)
225
+ .attr("selected", false)
226
+ .attr("disabled", true);
227
+
228
+ if(options.hideWhenAdded) $option.hide();
229
+ // if($.browser.msie) $select.hide().show(); // this forces IE to update display
230
+ if(true) $select.hide().show(); // this forces IE to update display
231
+ }
232
+
233
+ function enableSelectOption($option) {
234
+
235
+ // given an already disabled select option, enable it
236
+
237
+ $option.removeClass(options.optionDisabledClass)
238
+ .attr("disabled", false);
239
+
240
+ if(options.hideWhenAdded) $option.show();
241
+ // if($.browser.msie) $select.hide().show(); // this forces IE to update display
242
+ if(true) $select.hide().show(); // this forces IE to update display
243
+ }
244
+
245
+ function addListItem(optionId) {
246
+
247
+ // add a new item to the html list
248
+
249
+ var $O = $('#' + optionId);
250
+
251
+ if(!$O) return; // this is the first item, selectLabel
252
+
253
+ var $removeLink = $("<a></a>")
254
+ .attr("href", "#")
255
+ .addClass(options.removeClass)
256
+ .prepend(options.removeLabel)
257
+ .click(function() {
258
+ dropListItem($(this).parent('li').attr('rel'));
259
+ return false;
260
+ });
261
+
262
+ var $itemLabel = $("<span></span>")
263
+ .addClass(options.listItemLabelClass)
264
+ .html($O.html());
265
+
266
+ var $item = $("<li></li>")
267
+ .attr('rel', optionId)
268
+ .addClass(options.listItemClass)
269
+ .append($itemLabel)
270
+ .append($removeLink)
271
+ .hide();
272
+
273
+ if(!buildingSelect) {
274
+ if($O.is(":selected")) return; // already have it
275
+ $O.attr('selected', true);
276
+ }
277
+
278
+ if(options.addItemTarget == 'top' && !buildingSelect) {
279
+ $ol.prepend($item);
280
+ if(options.sortable) $original.prepend($O);
281
+ } else {
282
+ $ol.append($item);
283
+ if(options.sortable) $original.append($O);
284
+ }
285
+
286
+ addListItemShow($item);
287
+
288
+ disableSelectOption($("[rel=" + optionId + "]", $select));
289
+
290
+ if(!buildingSelect) {
291
+ setHighlight($item, options.highlightAddedLabel);
292
+ selectFirstItem();
293
+ if(options.sortable) $ol.sortable("refresh");
294
+ }
295
+
296
+ }
297
+
298
+ function addListItemShow($item) {
299
+
300
+ // reveal the currently hidden item with optional animation
301
+ // used only by addListItem()
302
+
303
+ if(options.animate && !buildingSelect) {
304
+ $item.animate({
305
+ opacity: "show",
306
+ height: "show"
307
+ }, 100, "swing", function() {
308
+ $item.animate({
309
+ height: "+=2px"
310
+ }, 50, "swing", function() {
311
+ $item.animate({
312
+ height: "-=2px"
313
+ }, 25, "swing");
314
+ });
315
+ });
316
+ } else {
317
+ $item.show();
318
+ }
319
+ }
320
+
321
+ function dropListItem(optionId, highlightItem) {
322
+
323
+ // remove an item from the html list
324
+
325
+ if(highlightItem == undefined) var highlightItem = true;
326
+ var $O = $('#' + optionId);
327
+
328
+ $O.attr('selected', false);
329
+ $item = $ol.children("li[rel=" + optionId + "]");
330
+
331
+ dropListItemHide($item);
332
+ enableSelectOption($("[rel=" + optionId + "]", options.removeWhenAdded ? $selectRemoved : $select));
333
+
334
+ if(highlightItem) setHighlight($item, options.highlightRemovedLabel);
335
+
336
+ triggerOriginalChange(optionId, 'drop');
337
+
338
+ }
339
+
340
+ function dropListItemHide($item) {
341
+
342
+ // remove the currently visible item with optional animation
343
+ // used only by dropListItem()
344
+
345
+ if(options.animate && !buildingSelect) {
346
+
347
+ $prevItem = $item.prev("li");
348
+
349
+ $item.animate({
350
+ opacity: "hide",
351
+ height: "hide"
352
+ }, 100, "linear", function() {
353
+ $prevItem.animate({
354
+ height: "-=2px"
355
+ }, 50, "swing", function() {
356
+ $prevItem.animate({
357
+ height: "+=2px"
358
+ }, 100, "swing");
359
+ });
360
+ $item.remove();
361
+ });
362
+
363
+ } else {
364
+ $item.remove();
365
+ }
366
+ }
367
+
368
+ function setHighlight($item, label) {
369
+
370
+ // set the contents of the highlight area that appears
371
+ // directly after the <select> single
372
+ // fade it in quickly, then fade it out
373
+
374
+ if(!options.highlight) return;
375
+
376
+ $select.next("#" + options.highlightClass + index).remove();
377
+
378
+ var $highlight = $("<span></span>")
379
+ .hide()
380
+ .addClass(options.highlightClass)
381
+ .attr('id', options.highlightClass + index)
382
+ .html(label + $item.children("." + options.listItemLabelClass).slice(0,1).text());
383
+
384
+ $select.after($highlight);
385
+
386
+ $highlight.fadeIn("fast", function() {
387
+ setTimeout(function() { $highlight.fadeOut("slow"); }, 50);
388
+ });
389
+ }
390
+
391
+ function triggerOriginalChange(optionId, type) {
392
+
393
+ // trigger a change event on the original select multiple
394
+ // so that other scripts can pick them up
395
+
396
+ ignoreOriginalChangeEvent = true;
397
+ $option = $("#" + optionId);
398
+
399
+ $original.trigger('change', [{
400
+ 'option': $option,
401
+ 'value': $option.val(),
402
+ 'id': optionId,
403
+ 'item': $ol.children("[rel=" + optionId + "]"),
404
+ 'type': type
405
+ }]);
406
+ }
407
+
408
+ init();
409
+ });
410
+ };
411
+
412
+ })(jQuery);
@@ -0,0 +1,7 @@
1
+ /*! jQuery UI - v1.12.1 - 2018-02-23
2
+ * http://jqueryui.com
3
+ * Includes: widget.js, data.js, scroll-parent.js, widgets/sortable.js, widgets/mouse.js
4
+ * Copyright jQuery Foundation and other contributors; Licensed MIT */
5
+
6
+ (function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e=0,i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},l=e.split(".")[0];e=e.split(".")[1];var h=l+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][h.toLowerCase()]=function(e){return!!t.data(e,h)},t[l]=t[l]||{},n=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:l,widgetName:e,widgetFullName:h}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var s,n,o=i.call(arguments,1),a=0,r=o.length;r>a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var s=!1;t(document).on("mouseup",function(){s=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!s){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,n=1===e.which,o="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),s=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,s=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.widget("ui.sortable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return t>=e&&e+i>t},_isFloating:function(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))},_create:function(){this.containerCache={},this._addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(t,e){this._super(t,e),"handle"===t&&this._setHandleClassName()},_setHandleClassName:function(){var e=this;this._removeClass(this.element.find(".ui-sortable-handle"),"ui-sortable-handle"),t.each(this.items,function(){e._addClass(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item,"ui-sortable-handle")})},_destroy:function(){this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):void 0}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("<style>*{ cursor: "+a.cursor+" !important; }</style>").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-this.document.scrollTop()<a.scrollSensitivity?r=this.document.scrollTop(this.document.scrollTop()-a.scrollSpeed):this.window.height()-(e.pageY-this.document.scrollTop())<a.scrollSensitivity&&(r=this.document.scrollTop(this.document.scrollTop()+a.scrollSpeed)),e.pageX-this.document.scrollLeft()<a.scrollSensitivity?r=this.document.scrollLeft(this.document.scrollLeft()-a.scrollSpeed):this.window.width()-(e.pageX-this.document.scrollLeft())<a.scrollSensitivity&&(r=this.document.scrollLeft(this.document.scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp(new t.Event("mouseup",{target:null})),"original"===this.options.helper?(this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,l=r+t.height,h=this.offset.click.top,c=this.offset.click.left,u="x"===this.options.axis||s+h>r&&l>s+h,d="y"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&l>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var e,i,s="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),n="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),o=s&&n;return o?(e=this._getDragVerticalDirection(),i=this._getDragHorizontalDirection(),this.floating?"right"===i||"down"===e?2:1:e&&("down"===e?2:1)):!1},_intersectsWithSides:function(t){var e=this._isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&e||"up"===s&&!e)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){function i(){r.push(this)}var s,n,o,a,r=[],l=[],h=this._connectWith();if(h&&e)for(s=h.length-1;s>=0;s--)for(o=t(h[s],this.document[0]),n=o.length-1;n>=0;n--)a=t.data(o[n],this.widgetFullName),a&&a!==this&&!a.options.disabled&&l.push([t.isFunction(a.options.items)?a.options.items.call(a.element):t(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a]);for(l.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=l.length-1;s>=0;s--)l[s][0].each(i);return t(r)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,l,h,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i],this.document[0]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,h=r.length;h>s;s++)l=t(r[s]),l.data(this.widgetName+"-item",a),c.push({item:l,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t("<"+s+">",e.document[0]);return e._addClass(n,"ui-sortable-placeholder",i||e.currentItem[0].className)._removeClass(n,"ui-sortable-helper"),"tbody"===s?e._createTrPlaceholder(e.currentItem.find("tr").eq(0),t("<tr>",e.document[0]).appendTo(n)):"tr"===s?e._createTrPlaceholder(e.currentItem,n):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_createTrPlaceholder:function(e,i){var s=this;e.children().each(function(){t("<td>&#160;</td>",s.document[0]).attr("colspan",t(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(e){var i,s,n,o,a,r,l,h,c,u,d=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!t.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(d&&t.contains(this.containers[i].element[0],d.element[0]))continue;d=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",e,this._uiHash(this)),this.containers[i].containerCache.over=0);if(d)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,o=null,c=d.floating||this._isFloating(this.currentItem),a=c?"left":"top",r=c?"width":"height",u=c?"pageX":"pageY",s=this.items.length-1;s>=0;s--)t.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(l=this.items[s].item.offset()[a],h=!1,e[u]-l>this.items[s][r]/2&&(h=!0),n>Math.abs(e[u]-l)&&(n=Math.abs(e[u]-l),o=this.items[s],this.direction=h?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",e,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;o?this._rearrange(e,o,null,!0):this._rearrange(e,null,this.containers[p].element,!0),this._trigger("change",e,this._uiHash()),this.containers[p]._trigger("change",e,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",e,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.height()||document.body.parentNode.scrollHeight:this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}
7
+ },_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,l=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():l?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():l?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){function i(t,e,i){return function(s){i._trigger(t,s,e._uiHash(e))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&n.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||n.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(n.push(function(t){this._trigger("remove",t,this._uiHash())}),n.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)e||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(s=0;n.length>s;s++)n[s].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})});
@@ -262,6 +262,9 @@ $(function() {
262
262
  // Init textareas
263
263
  $('textarea',c).trigger('input');
264
264
 
265
+ // Init multiple select with asmSelect
266
+ $('select[multiple]').asmSelect({ sortable: true, removeLabel: '&times;' });
267
+
265
268
  }; // End - Init column
266
269
 
267
270
  // Init finder
@@ -4,6 +4,8 @@
4
4
  <head>
5
5
  <meta charset="utf-8" />
6
6
  <title><%= settings.meta_title %></title>
7
+ <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/jquery-ui.min.css" type="text/css" media='screen' />
8
+ <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/asmselect.css" type="text/css" media='screen' />
7
9
  <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/main.css" type="text/css" media='screen' />
8
10
  </head>
9
11
  <body>
@@ -40,10 +42,7 @@
40
42
 
41
43
  <script id="template-document" type="x-tmpl-mustache">
42
44
  <li class='admin-list-item' data-id='{{id}}'>
43
- <header>
44
- {{#sort_field}}
45
- <span class='handle' title='Drag and drop'>&#8597;</span>
46
- {{/sort_field}}
45
+ <header class='{{#sort_field}}handle{{/sort_field}}' title='{{#sort_field}}Drag and drop{{/sort_field}}'>
47
46
  <button type='button' class='admin-delete' title='Delete' value='<%= request.script_name %>/api/{{admin_url}}'>&times;</button>
48
47
  </header>
49
48
  <a href="<%= request.script_name %>/form/{{admin_url}}" class='column-push' title='Edit'>
@@ -75,7 +74,7 @@
75
74
  <script id="template-nested-form" type="x-tmpl-mustache">
76
75
  <li>
77
76
  <header>
78
- <span class='handle' title='Drag and drop'>&#8597;</span><button type='button' class='admin-delete-nested' title='Delete'>&times;</button>
77
+ <span class='handle handle-button' title='Drag and drop'>&#8597;</span><button type='button' class='admin-delete-nested' title='Delete'>&times;</button>
79
78
  </header>
80
79
  {{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
81
80
  </li>
@@ -116,7 +115,10 @@
116
115
  </script>
117
116
 
118
117
  <script id="template-select-field" type="x-tmpl-mustache">
119
- <select name='{{input_name}}' {{#multiple}}multiple{{/multiple}}{{{build_input_attributes}}}>
118
+ {{#multiple}}
119
+ <input type='hidden' name='{{input_name}}' value='nil' />
120
+ {{/multiple}}
121
+ <select name='{{input_name}}' {{#multiple}}multiple title='?'{{/multiple}}{{{build_input_attributes}}}>
120
122
  {{#select_options}}
121
123
  <option value='{{value}}' {{#selected}}selected{{/selected}}>{{description}}</option>
122
124
  {{/select_options}}
@@ -150,9 +152,11 @@
150
152
 
151
153
  <!-- JS -->
152
154
  <script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript" charset="utf-8"></script>
155
+ <script src="<%= request.script_name %>/__assets__/js/jquery-ui.min.js" type="text/javascript" charset="utf-8"></script>
153
156
  <script src="<%= request.script_name %>/__assets__/js/mustache.js" type="text/javascript" charset="utf-8"></script>
154
157
  <script src="<%= request.script_name %>/__assets__/js/columnav.js" type="text/javascript" charset="utf-8"></script>
155
- <script src="<%= request.script_name %>/__assets__/js/sortable.js" type="text/javascript" charset="utf-8"></script>
158
+ <%# <script src="<%= request.script_name %1>/__assets__/js/sortable.js" type="text/javascript" charset="utf-8"></script> %>
159
+ <script src="<%= request.script_name %>/__assets__/js/asmselect.js" type="text/javascript" charset="utf-8"></script>
156
160
  <script type="text/javascript">
157
161
  window.admin_path = "<%= request.script_name %>";
158
162
  window.index_path = "<%= settings.index_path %>";
@@ -99,7 +99,7 @@ module PopulateMe
99
99
  persistent_instance_variables.inject({'_class'=>self.class.name}) do |h,var|
100
100
  k = var.to_s[1..-1]
101
101
  v = instance_variable_get var
102
- if v.is_a? Array
102
+ if is_nested_docs?(v)
103
103
  h[k] = v.map(&:to_h)
104
104
  else
105
105
  h[k] = v
@@ -118,7 +118,7 @@ module PopulateMe
118
118
  persistent_instance_variables.map do |var|
119
119
  instance_variable_get var
120
120
  end.find_all do |val|
121
- val.is_a? Array
121
+ is_nested_docs?(val)
122
122
  end.flatten
123
123
  end
124
124
 
@@ -152,7 +152,7 @@ module PopulateMe
152
152
  hash.delete('_class')
153
153
  hash.each do |k,v|
154
154
  getter = k.to_sym
155
- if v.is_a? Array
155
+ if is_nested_hash_docs?(v)
156
156
  break unless respond_to?(getter)
157
157
  __send__(getter).clear
158
158
  v.each do |d|
@@ -173,6 +173,19 @@ module PopulateMe
173
173
  end
174
174
  self.settings = OpenStruct.new
175
175
 
176
+ private
177
+
178
+ def is_nested_docs? val
179
+ # Differenciate nested docs array from other king of array
180
+ val.is_a?(Array) and !val.empty? and val[0].is_a?(PopulateMe::Document)
181
+ end
182
+
183
+ def is_nested_hash_docs? val
184
+ # Differenciate nested docs array from other king of array
185
+ # when from a hash
186
+ val.is_a?(Array) and !val.empty? and val[0].is_a?(Hash) and val[0].has_key?('_class')
187
+ end
188
+
176
189
  end
177
190
  end
178
191
 
@@ -40,10 +40,8 @@ module PopulateMe
40
40
  }
41
41
  self.class.complete_field_options :_class, class_item
42
42
  items = self.class.fields.inject([class_item]) do |out,(k,item)|
43
- item = item.dup
44
43
  if item[:form_field]
45
- outcast k, item, o
46
- out << item
44
+ out << outcast(k, item, o)
47
45
  end
48
46
  out
49
47
  end
@@ -8,23 +8,34 @@ module PopulateMe
8
8
  # Therefore, it is a complement to the AdminAdapter module.
9
9
 
10
10
  def outcast field, item, o={}
11
+ item = item.dup
11
12
  item[:input_name] = "#{o[:input_name_prefix]}[#{item[:field_name]}]"
12
13
  unless item[:type]==:list
13
14
  WebUtils.ensure_key! item, :input_value, self.__send__(field)
14
15
  end
15
16
  meth = "outcast_#{item[:type]}".to_sym
16
- __send__(meth, field, item, o) if respond_to?(meth)
17
+ if respond_to?(meth)
18
+ __send__(meth, field, item, o)
19
+ else
20
+ item
21
+ end
17
22
  end
18
23
 
19
24
  def outcast_list field, item, o={}
25
+ item = item.dup
20
26
  item[:items] = self.__send__(field).map do |nested|
21
27
  nested.to_admin_form(o.merge(input_name_prefix: item[:input_name]+'[]'))
22
28
  end
29
+ item
23
30
  end
24
31
 
25
32
  def outcast_select field, item, o={}
33
+ item = item.dup
26
34
  unless item[:select_options].nil?
27
- opts = WebUtils.get_value(item[:select_options],self).dup
35
+ if item[:multiple]==true
36
+ item[:input_name] = item[:input_name]+'[]'
37
+ end
38
+ opts = WebUtils.deep_copy(WebUtils.get_value(item[:select_options],self))
28
39
  opts.map! do |opt|
29
40
  if opt.is_a?(String)||opt.is_a?(Symbol)
30
41
  opt = [opt.to_s.capitalize,opt]
@@ -32,15 +43,30 @@ module PopulateMe
32
43
  if opt.is_a?(Array)
33
44
  opt = {description: opt[0].to_s, value: opt[1].to_s}
34
45
  end
35
- opt[:selected] = true if item[:input_value]==opt[:value]
46
+ if item[:input_value].respond_to?(:include?)
47
+ opt[:selected] = true if item[:input_value].include?(opt[:value])
48
+ else
49
+ opt[:selected] = true if item[:input_value]==opt[:value]
50
+ end
36
51
  opt
37
52
  end
53
+ if item[:multiple]
54
+ (item[:input_value]||[]).reverse.each do |iv|
55
+ opt = opts.find{|opt| opt[:value]==iv }
56
+ opts.unshift(opts.delete(opt)) unless opt.nil?
57
+ end
58
+ end
38
59
  item[:select_options] = opts
60
+ item
61
+ else
62
+ item
39
63
  end
40
64
  end
41
65
 
42
66
  def outcast_attachment field, item, o={}
67
+ item = item.dup
43
68
  item[:url] = self.attachment(field).url
69
+ item
44
70
  end
45
71
 
46
72
  end
@@ -101,6 +101,19 @@ module PopulateMe
101
101
  end
102
102
  end
103
103
 
104
+ def to_select_options o={}
105
+ proc do
106
+ items = self.admin_find(query: {}, fields: [self.id_string_key, self.label_field])
107
+ output = items.sort_by do |i|
108
+ i.to_s.downcase
109
+ end.map do |i|
110
+ [i.to_s, i.id]
111
+ end
112
+ output.unshift(['?','']) if o[:allow_empty]
113
+ output
114
+ end
115
+ end
116
+
104
117
  end
105
118
 
106
119
  end
@@ -27,6 +27,14 @@ module PopulateMe
27
27
  WebUtils.parse_price(v)
28
28
  end
29
29
 
30
+ def typecast_select k, v
31
+ if v.is_a?(Array)
32
+ v.reject{|str| str=='nil' }
33
+ else
34
+ v
35
+ end
36
+ end
37
+
30
38
  def typecast_date k, v
31
39
  if v[/\d\d(\/|-)\d\d(\/|-)\d\d\d\d/]
32
40
  Date.parse v
@@ -27,7 +27,7 @@ module PopulateMe
27
27
  report = self._errors.dup || {}
28
28
  persistent_instance_variables.each do |var|
29
29
  value = instance_variable_get var
30
- if value.is_a? Array
30
+ if is_nested_docs?(value)
31
31
  k = var[1..-1].to_sym
32
32
  report[k] = []
33
33
  value.each do |d|
@@ -1,4 +1,4 @@
1
1
  module PopulateMe
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
4
4
 
@@ -0,0 +1,211 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+ require 'populate_me/attachment'
4
+
5
+ class Outcasted < PopulateMe::Document
6
+ set :default_attachment_class, PopulateMe::Attachment
7
+
8
+ field :name
9
+ field :size, type: :select, select_options: [
10
+ {description: 'small', value: 's'},
11
+ {description: 'medium', value: 'm'},
12
+ {description: 'large', value: 'l'}
13
+ ]
14
+ field :tags, type: :select, select_options: ['art','sport','science'], multiple: true
15
+ field :related_properties, type: :select, select_options: ['prop1','prop2','prop3'], multiple: true
16
+ field :pdf, type: :attachment
17
+ field :authors, type: :list
18
+ field :weirdo, type: :strange
19
+
20
+ def get_size_options
21
+ [
22
+ [:small, :s],
23
+ [:medium, :m],
24
+ [:large, :l]
25
+ ]
26
+ end
27
+
28
+ end
29
+
30
+ class Outcasted::Author < PopulateMe::Document
31
+ field :name
32
+ end
33
+
34
+ describe PopulateMe::Document, 'Outcasting' do
35
+
36
+ parallelize_me!
37
+
38
+ describe '#outcast' do
39
+
40
+ it 'Keeps the original info unchanged' do
41
+ original = Outcasted.fields[:name]
42
+ output = Outcasted.new.outcast(:name, original, {input_name_prefix: 'data'})
43
+ refute original.equal?(output)
44
+ end
45
+
46
+ it 'Adds input_name_prefix to input_name' do
47
+ original = Outcasted.fields[:name]
48
+ output = Outcasted.new.outcast(:name, original, {input_name_prefix: 'data'})
49
+ assert_equal 'data[name]', output[:input_name]
50
+ end
51
+
52
+ it 'Sets input_value to the field value' do
53
+ original = Outcasted.fields[:name]
54
+ outcasted = Outcasted.new
55
+ outcasted.name = 'Thomas'
56
+ output = outcasted.outcast(:name, original, {input_name_prefix: 'data'})
57
+ assert_equal 'Thomas', output[:input_value]
58
+ end
59
+
60
+ end
61
+
62
+ describe '#outcast_list' do
63
+
64
+ it 'Has no value and an empty list of items when list is empty' do
65
+ original = Outcasted.fields[:authors]
66
+ output = Outcasted.new.outcast(:authors, original, {input_name_prefix: 'data'})
67
+ assert_nil output[:input_value]
68
+ assert_equal [], output[:items]
69
+ end
70
+
71
+ it 'Nests the items in the list with their own nested prefix' do
72
+ original = Outcasted.fields[:authors]
73
+ outcasted = Outcasted.new
74
+ outcasted.authors.push(Outcasted::Author.new(name: 'Bob'))
75
+ outcasted.authors.push(Outcasted::Author.new(name: 'Mould'))
76
+ output = outcasted.outcast(:authors, original, {input_name_prefix: 'data'})
77
+ assert_nil output[:input_value]
78
+ assert_equal 2, output[:items].size
79
+ first_item = output[:items][0][:fields]
80
+ assert_equal 'data[authors][][name]', first_item[1][:input_name]
81
+ assert_equal 'Bob', first_item[1][:input_value]
82
+ end
83
+
84
+ end
85
+
86
+ describe '#outcast_select' do
87
+
88
+ def formated_options? original, output
89
+ assert_equal [ {description: 'small', value: 's'}, {description: 'medium', value: 'm'}, {description: 'large', value: 'l'} ], output[:select_options]
90
+ refute original[:select_options].equal?(output[:select_options])
91
+ assert output[:select_options].all?{|o|!o[:selected]}
92
+ end
93
+
94
+ it 'Leaves the options as they are when they are already formated' do
95
+ original = Outcasted.fields[:size]
96
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
97
+ formated_options?(original, output)
98
+ end
99
+
100
+ it 'Formats the options when it is a 2 strings array' do
101
+ original = Outcasted.fields[:size].dup
102
+ original[:select_options] = [
103
+ [:small, :s],
104
+ [:medium, :m],
105
+ [:large, :l]
106
+ ]
107
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
108
+ formated_options?(original, output)
109
+ end
110
+
111
+ it 'Formats the options when they come from a proc' do
112
+ original = Outcasted.fields[:size].dup
113
+ original[:select_options] = proc{[
114
+ [:small, :s],
115
+ [:medium, :m],
116
+ [:large, :l]
117
+ ]}
118
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
119
+ formated_options?(original, output)
120
+ end
121
+
122
+ it 'Formats the options when they come from a symbol' do
123
+ original = Outcasted.fields[:size].dup
124
+ original[:select_options] = :get_size_options
125
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
126
+ formated_options?(original, output)
127
+ end
128
+
129
+ it 'Formats options when it comes from a simple string' do
130
+ original = Outcasted.fields[:size].dup
131
+ original[:select_options] = [:small,:medium,:large]
132
+ output = Outcasted.new.outcast(:size, original, {input_name_prefix: 'data'})
133
+ assert_equal [ {description: 'Small', value: 'small'}, {description: 'Medium', value: 'medium'}, {description: 'Large', value: 'large'} ], output[:select_options]
134
+ end
135
+
136
+ it 'Selects the input value' do
137
+ original = Outcasted.fields[:size]
138
+ outcasted = Outcasted.new size: 'm'
139
+ output = outcasted.outcast(:size, original, {input_name_prefix: 'data'})
140
+ assert_equal 1, output[:select_options].select{|o|o[:selected]}.size
141
+ assert output[:select_options].find{|o|o[:value]=='m'}[:selected]
142
+ end
143
+
144
+ it 'Adds [] at the end of input_name if multiple is true' do
145
+ original = Outcasted.fields[:tags]
146
+ output = Outcasted.new.outcast(:tags, original, {input_name_prefix: 'data'})
147
+ assert_equal 'data[tags][]', output[:input_name]
148
+ end
149
+
150
+ it 'Selects multiple options when input_value is an array' do
151
+ original = Outcasted.fields[:tags]
152
+ outcasted = Outcasted.new tags: ['art','science']
153
+ output = outcasted.outcast(:tags, original, {input_name_prefix: 'data'})
154
+ assert output[:select_options].find{|o|o[:value]=='art'}[:selected]
155
+ assert output[:select_options].find{|o|o[:value]=='science'}[:selected]
156
+ refute output[:select_options].find{|o|o[:value]=='sport'}[:selected]
157
+ end
158
+
159
+ it 'Orders input values at the begining when multiple options' do
160
+ original = Outcasted.fields[:related_properties]
161
+
162
+ # Normal
163
+ outcasted = Outcasted.new related_properties: ['prop3','prop1']
164
+ output = outcasted.outcast(:related_properties, original, {input_name_prefix: 'data'})
165
+ assert_equal 'prop3', output[:select_options][0][:value]
166
+ assert output[:select_options][0][:selected]
167
+ assert_equal 'prop1', output[:select_options][1][:value]
168
+ assert output[:select_options][1][:selected]
169
+ assert_equal 'prop2', output[:select_options][2][:value]
170
+ refute output[:select_options][2][:selected]
171
+
172
+ # When input_value is nil
173
+ outcasted = Outcasted.new
174
+ output = outcasted.outcast(:related_properties, original, {input_name_prefix: 'data'})
175
+ assert_equal 'prop1', output[:select_options][0][:value]
176
+ refute output[:select_options][0][:selected]
177
+ assert_equal 'prop2', output[:select_options][1][:value]
178
+ refute output[:select_options][1][:selected]
179
+ assert_equal 'prop3', output[:select_options][2][:value]
180
+ refute output[:select_options][2][:selected]
181
+
182
+ # When input_value has a non existing value
183
+ outcasted = Outcasted.new related_properties: ['stale','prop2']
184
+ output = outcasted.outcast(:related_properties, original, {input_name_prefix: 'data'})
185
+ assert_equal 'prop2', output[:select_options][0][:value]
186
+ assert output[:select_options][0][:selected]
187
+ assert_equal 'prop1', output[:select_options][1][:value]
188
+ refute output[:select_options][1][:selected]
189
+ assert_equal 'prop3', output[:select_options][2][:value]
190
+ refute output[:select_options][2][:selected]
191
+ end
192
+
193
+ end
194
+
195
+ describe '#outcast_attachment' do
196
+
197
+ it 'Sets url' do
198
+ original = Outcasted.fields[:pdf]
199
+ outcasted = Outcasted.new
200
+ output = outcasted.outcast(:pdf, original, {input_name_prefix: 'data'})
201
+ assert_nil output[:url]
202
+
203
+ outcasted.pdf = 'guidelines.pdf'
204
+ output = outcasted.outcast(:pdf, original, {input_name_prefix: 'data'})
205
+ assert_equal outcasted.attachment(:pdf).url, output[:url]
206
+ end
207
+
208
+ end
209
+
210
+ end
211
+
@@ -77,5 +77,47 @@ describe PopulateMe::Document, 'Schema' do
77
77
 
78
78
  end
79
79
 
80
+ describe '::to_select_options' do
81
+
82
+ class Selectoptionable < PopulateMe::Document
83
+ field :name
84
+ field :slug
85
+ label :slug
86
+ end
87
+
88
+ before do
89
+ Selectoptionable.documents = []
90
+ Selectoptionable.new(id: '1', name: 'Joe', slug: 'joe').save
91
+ Selectoptionable.new(id: '2', name: 'William', slug: 'william').save
92
+ Selectoptionable.new(id: '3', name: 'Jack', slug: 'jack').save
93
+ Selectoptionable.new(id: '4', name: 'Averell', slug: 'averell').save
94
+ end
95
+
96
+ after do
97
+ Selectoptionable.documents = []
98
+ end
99
+
100
+ it 'Formats all items for a select_options' do
101
+ output_proc = Selectoptionable.to_select_options
102
+ assert output_proc.is_a?(Proc)
103
+ output = output_proc.call
104
+ assert_equal 4, output.size
105
+ assert output.all?{|o| o.is_a?(Array) and o.size==2}
106
+ assert_equal '1', output.find{|o|o[0]=='joe'}[1]
107
+ end
108
+
109
+ it 'Puts items in alphabetical order of their label' do
110
+ output= Selectoptionable.to_select_options.call
111
+ assert_equal ['averell', '4'], output[0]
112
+ end
113
+
114
+ it 'Has an option for prepending empty choice' do
115
+ output= Selectoptionable.to_select_options(allow_empty: true).call
116
+ assert_equal ['?', ''], output[0]
117
+ assert_equal ['averell', '4'], output[1]
118
+ end
119
+
120
+ end
121
+
80
122
  end
81
123
 
@@ -6,6 +6,8 @@ class Person < PopulateMe::Document
6
6
  field :shared, type: :boolean
7
7
  field :age, type: :integer
8
8
  field :salary, type: :price
9
+ field :size, type: :select, select_option: ['S','M','L']
10
+ field :categories, type: :select, select_option: ['A','B','C'], multiple: true
9
11
  field :dob, type: :date
10
12
  field :when, type: :datetime
11
13
  end
@@ -85,6 +87,14 @@ describe PopulateMe::Document, 'Typecasting' do
85
87
  end
86
88
  end
87
89
 
90
+ describe "Field has type :select" do
91
+ it "Removes 'nil' string if multiple/array" do
92
+ assert_equal 'M', subject.typecast(:size, 'M')
93
+ assert_equal ['A','B'], subject.typecast(:categories, ['A','B'])
94
+ assert_equal ['A','B'], subject.typecast(:categories, ['nil','A','B'])
95
+ end
96
+ end
97
+
88
98
  describe "Field has type :date" do
89
99
  it "Parses the date with Date.parse" do
90
100
  assert_equal Date.parse('10/11/1979'), subject.typecast(:dob,'10/11/1979')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: populate-me
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mickael Riga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-27 00:00:00.000000000 Z
11
+ date: 2018-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: web-utils
@@ -195,8 +195,12 @@ files:
195
195
  - example/config.ru
196
196
  - lib/populate_me.rb
197
197
  - lib/populate_me/admin.rb
198
+ - lib/populate_me/admin/__assets__/css/asmselect.css
199
+ - lib/populate_me/admin/__assets__/css/jquery-ui.min.css
198
200
  - lib/populate_me/admin/__assets__/css/main.css
201
+ - lib/populate_me/admin/__assets__/js/asmselect.js
199
202
  - lib/populate_me/admin/__assets__/js/columnav.js
203
+ - lib/populate_me/admin/__assets__/js/jquery-ui.min.js
200
204
  - lib/populate_me/admin/__assets__/js/main.js
201
205
  - lib/populate_me/admin/__assets__/js/mustache.js
202
206
  - lib/populate_me/admin/__assets__/js/sortable.js
@@ -225,6 +229,7 @@ files:
225
229
  - test/test_document.rb
226
230
  - test/test_document_admin_adapter.rb
227
231
  - test/test_document_callbacks.rb
232
+ - test/test_document_outcasting.rb
228
233
  - test/test_document_persistence.rb
229
234
  - test/test_document_schema.rb
230
235
  - test/test_document_typecasting.rb
@@ -265,6 +270,7 @@ test_files:
265
270
  - test/test_document.rb
266
271
  - test/test_document_admin_adapter.rb
267
272
  - test/test_document_callbacks.rb
273
+ - test/test_document_outcasting.rb
268
274
  - test/test_document_persistence.rb
269
275
  - test/test_document_schema.rb
270
276
  - test/test_document_typecasting.rb