populate-me 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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