decidim-gallery 0.0.1

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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +30 -0
  4. data/Rakefile +9 -0
  5. data/app/cells/decidim/gallery/main/image.erb +51 -0
  6. data/app/cells/decidim/gallery/main/image_collection.erb +13 -0
  7. data/app/cells/decidim/gallery/main/image_list.erb +9 -0
  8. data/app/cells/decidim/gallery/main/video.erb +20 -0
  9. data/app/cells/decidim/gallery/main/video_collection.erb +3 -0
  10. data/app/cells/decidim/gallery/main_cell.rb +88 -0
  11. data/app/cells/decidim/gallery/video/show.erb +6 -0
  12. data/app/cells/decidim/gallery/video_cell.rb +96 -0
  13. data/app/commands/decidim/gallery/admin/create_gallery_item.rb +50 -0
  14. data/app/commands/decidim/gallery/admin/publish_gallery_item.rb +38 -0
  15. data/app/commands/decidim/gallery/admin/unpublish_gallery_item.rb +38 -0
  16. data/app/commands/decidim/gallery/admin/update_gallery_item.rb +44 -0
  17. data/app/controllers/decidim/gallery/admin/application_controller.rb +24 -0
  18. data/app/controllers/decidim/gallery/admin/gallery_item_controller.rb +104 -0
  19. data/app/controllers/decidim/gallery/application_controller.rb +8 -0
  20. data/app/controllers/decidim/gallery/gallery_controller.rb +9 -0
  21. data/app/forms/decidim/gallery/admin/gallery_item_form.rb +17 -0
  22. data/app/forms/decidim/gallery/admin/gallery_item_image_form.rb +14 -0
  23. data/app/forms/decidim/gallery/admin/gallery_item_video_form.rb +12 -0
  24. data/app/helpers/decidim/gallery/admin/application_helper.rb +18 -0
  25. data/app/model/decidim/gallery/application_record.rb +10 -0
  26. data/app/model/decidim/gallery/gallery_item.rb +38 -0
  27. data/app/overrides/decidim/admin/static_pages/edit/add_gallery.html.erb.deface +11 -0
  28. data/app/overrides/decidim/pages/_standalone/add_content_blocks.html.erb.deface +7 -0
  29. data/app/overrides/decidim/pages/_tabbed/add_content_blocks.html.erb.deface +5 -0
  30. data/app/packs/entrypoints/decidim_gallery.js +3 -0
  31. data/app/packs/images/decidim/gallery/icon.svg +1 -0
  32. data/app/packs/src/decidim/gallery/gallery.js +42 -0
  33. data/app/packs/src/decidim/gallery/masonry/EvEmitter.js +85 -0
  34. data/app/packs/src/decidim/gallery/masonry/getSize.js +181 -0
  35. data/app/packs/src/decidim/gallery/masonry/jQueryBridget.js +109 -0
  36. data/app/packs/src/decidim/gallery/masonry/masonry.js +202 -0
  37. data/app/packs/src/decidim/gallery/masonry/matchesSelector.js +25 -0
  38. data/app/packs/src/decidim/gallery/masonry/outlayer.js +885 -0
  39. data/app/packs/src/decidim/gallery/masonry/outlayerItem.js +522 -0
  40. data/app/packs/src/decidim/gallery/masonry/utils.js +203 -0
  41. data/app/packs/stylesheets/decidim/gallery/_gallery.scss +3 -0
  42. data/app/permissions/decidim/gallery/admin/permissions.rb +18 -0
  43. data/app/permissions/decidim/gallery/permissions.rb +17 -0
  44. data/app/views/decidim/gallery/admin/gallery_item/_form_image.html.erb +16 -0
  45. data/app/views/decidim/gallery/admin/gallery_item/_form_video.html.erb +16 -0
  46. data/app/views/decidim/gallery/admin/gallery_item/edit.html.erb +9 -0
  47. data/app/views/decidim/gallery/admin/gallery_item/index.html.erb +52 -0
  48. data/app/views/decidim/gallery/admin/gallery_item/new.html.erb +9 -0
  49. data/app/views/decidim/gallery/gallery/index.html.erb +11 -0
  50. data/config/assets.rb +9 -0
  51. data/config/i18n-tasks.yml +22 -0
  52. data/config/locales/en.yml +79 -0
  53. data/config/locales/fr.yml +79 -0
  54. data/config/locales/ro.yml +79 -0
  55. data/lib/decidim/gallery/admin/static_pages/command.rb +15 -0
  56. data/lib/decidim/gallery/admin/static_pages/form.rb +21 -0
  57. data/lib/decidim/gallery/admin.rb +14 -0
  58. data/lib/decidim/gallery/admin_engine.rb +27 -0
  59. data/lib/decidim/gallery/component.rb +47 -0
  60. data/lib/decidim/gallery/engine.rb +60 -0
  61. data/lib/decidim/gallery/test/factories.rb +58 -0
  62. data/lib/decidim/gallery/version.rb +14 -0
  63. data/lib/decidim/gallery.rb +14 -0
  64. data/lib/tasks/decidim_gallery.rake +16 -0
  65. metadata +163 -0
@@ -0,0 +1,885 @@
1
+ import getSize from "./getSize";
2
+ import jQueryBridget from "./jQueryBridget";
3
+ import Item from "./outlayerItem";
4
+ import EvEmitter from "./EvEmitter";
5
+ import utils from "./utils";
6
+
7
+ // globally unique identifiers
8
+ let GUID = 0;
9
+ // internal store of all Outlayer intances
10
+ let instances = {};
11
+ let noop = function () {};
12
+
13
+ function Outlayer( element, options ) {
14
+ var queryElement = utils.getQueryElement( element );
15
+ if ( !queryElement ) {
16
+ if ( console ) {
17
+ console.error( 'Bad element for ' + this.constructor.namespace +
18
+ ': ' + ( queryElement || element ) );
19
+ }
20
+ return;
21
+ }
22
+ this.element = queryElement;
23
+ // add jQuery
24
+ if ( jQuery ) {
25
+ this.$element = jQuery( this.element );
26
+ }
27
+
28
+ // options
29
+ this.options = utils.extend( {}, this.constructor.defaults );
30
+ this.option( options );
31
+
32
+ // add id for Outlayer.getFromElement
33
+ var id = ++GUID;
34
+ this.element.outlayerGUID = id; // expando
35
+ instances[ id ] = this; // associate via id
36
+
37
+ // kick it off
38
+ this._create();
39
+
40
+ var isInitLayout = this._getOption('initLayout');
41
+ if ( isInitLayout ) {
42
+ this.layout();
43
+ }
44
+ }
45
+
46
+ // settings are for internal use only
47
+ Outlayer.namespace = 'outlayer';
48
+ Outlayer.Item = Item;
49
+
50
+ // default options
51
+ Outlayer.defaults = {
52
+ containerStyle: {
53
+ position: 'relative'
54
+ },
55
+ initLayout: true,
56
+ originLeft: true,
57
+ originTop: true,
58
+ resize: true,
59
+ resizeContainer: true,
60
+ // item options
61
+ transitionDuration: '0.4s',
62
+ hiddenStyle: {
63
+ opacity: 0,
64
+ transform: 'scale(0.001)'
65
+ },
66
+ visibleStyle: {
67
+ opacity: 1,
68
+ transform: 'scale(1)'
69
+ }
70
+ };
71
+
72
+ var proto = Outlayer.prototype;
73
+ // inherit EvEmitter
74
+ utils.extend( proto, EvEmitter.prototype );
75
+
76
+ /**
77
+ * set options
78
+ * @param {Object} opts
79
+ */
80
+ proto.option = function( opts ) {
81
+ utils.extend( this.options, opts );
82
+ };
83
+
84
+ /**
85
+ * get backwards compatible option value, check old name
86
+ */
87
+ proto._getOption = function( option ) {
88
+ var oldOption = this.constructor.compatOptions[ option ];
89
+ return oldOption && this.options[ oldOption ] !== undefined ?
90
+ this.options[ oldOption ] : this.options[ option ];
91
+ };
92
+
93
+ Outlayer.compatOptions = {
94
+ // currentName: oldName
95
+ initLayout: 'isInitLayout',
96
+ horizontal: 'isHorizontal',
97
+ layoutInstant: 'isLayoutInstant',
98
+ originLeft: 'isOriginLeft',
99
+ originTop: 'isOriginTop',
100
+ resize: 'isResizeBound',
101
+ resizeContainer: 'isResizingContainer'
102
+ };
103
+
104
+ proto._create = function() {
105
+ // get items from children
106
+ this.reloadItems();
107
+ // elements that affect layout, but are not laid out
108
+ this.stamps = [];
109
+ this.stamp( this.options.stamp );
110
+ // set container style
111
+ utils.extend( this.element.style, this.options.containerStyle );
112
+
113
+ // bind resize method
114
+ var canBindResize = this._getOption('resize');
115
+ if ( canBindResize ) {
116
+ this.bindResize();
117
+ }
118
+ };
119
+
120
+ // goes through all children again and gets bricks in proper order
121
+ proto.reloadItems = function() {
122
+ // collection of item elements
123
+ this.items = this._itemize( this.element.children );
124
+ };
125
+
126
+
127
+ /**
128
+ * turn elements into Outlayer.Items to be used in layout
129
+ * @param {Array or NodeList or HTMLElement} elems
130
+ * @returns {Array} items - collection of new Outlayer Items
131
+ */
132
+ proto._itemize = function( elems ) {
133
+
134
+ var itemElems = this._filterFindItemElements( elems );
135
+ var Item = this.constructor.Item;
136
+
137
+ // create new Outlayer Items for collection
138
+ var items = [];
139
+ for ( var i=0; i < itemElems.length; i++ ) {
140
+ var elem = itemElems[i];
141
+ var item = new Item( elem, this );
142
+ items.push( item );
143
+ }
144
+
145
+ return items;
146
+ };
147
+
148
+ /**
149
+ * get item elements to be used in layout
150
+ * @param {Array or NodeList or HTMLElement} elems
151
+ * @returns {Array} items - item elements
152
+ */
153
+ proto._filterFindItemElements = function( elems ) {
154
+ return utils.filterFindElements( elems, this.options.itemSelector );
155
+ };
156
+
157
+ /**
158
+ * getter method for getting item elements
159
+ * @returns {Array} elems - collection of item elements
160
+ */
161
+ proto.getItemElements = function() {
162
+ return this.items.map( function( item ) {
163
+ return item.element;
164
+ });
165
+ };
166
+
167
+ // ----- init & layout ----- //
168
+
169
+ /**
170
+ * lays out all items
171
+ */
172
+ proto.layout = function() {
173
+ this._resetLayout();
174
+ this._manageStamps();
175
+
176
+ // don't animate first layout
177
+ var layoutInstant = this._getOption('layoutInstant');
178
+ var isInstant = layoutInstant !== undefined ?
179
+ layoutInstant : !this._isLayoutInited;
180
+ this.layoutItems( this.items, isInstant );
181
+
182
+ // flag for initalized
183
+ this._isLayoutInited = true;
184
+ };
185
+
186
+ // _init is alias for layout
187
+ proto._init = proto.layout;
188
+
189
+ /**
190
+ * logic before any new layout
191
+ */
192
+ proto._resetLayout = function() {
193
+ this.getSize();
194
+ };
195
+
196
+
197
+ proto.getSize = function() {
198
+ this.size = getSize( this.element );
199
+ };
200
+
201
+ /**
202
+ * get measurement from option, for columnWidth, rowHeight, gutter
203
+ * if option is String -> get element from selector string, & get size of element
204
+ * if option is Element -> get size of element
205
+ * else use option as a number
206
+ *
207
+ * @param {String} measurement
208
+ * @param {String} size - width or height
209
+ * @private
210
+ */
211
+ proto._getMeasurement = function( measurement, size ) {
212
+ var option = this.options[ measurement ];
213
+ var elem;
214
+ if ( !option ) {
215
+ // default to 0
216
+ this[ measurement ] = 0;
217
+ } else {
218
+ // use option as an element
219
+ if ( typeof option == 'string' ) {
220
+ elem = this.element.querySelector( option );
221
+ } else if ( option instanceof HTMLElement ) {
222
+ elem = option;
223
+ }
224
+ // use size of element, if element
225
+ this[ measurement ] = elem ? getSize( elem )[ size ] : option;
226
+ }
227
+ };
228
+
229
+ /**
230
+ * layout a collection of item elements
231
+ * @api public
232
+ */
233
+ proto.layoutItems = function( items, isInstant ) {
234
+ items = this._getItemsForLayout( items );
235
+
236
+ this._layoutItems( items, isInstant );
237
+
238
+ this._postLayout();
239
+ };
240
+
241
+ /**
242
+ * get the items to be laid out
243
+ * you may want to skip over some items
244
+ * @param {Array} items
245
+ * @returns {Array} items
246
+ */
247
+ proto._getItemsForLayout = function( items ) {
248
+ return items.filter( function( item ) {
249
+ return !item.isIgnored;
250
+ });
251
+ };
252
+
253
+ /**
254
+ * layout items
255
+ * @param {Array} items
256
+ * @param {Boolean} isInstant
257
+ */
258
+ proto._layoutItems = function( items, isInstant ) {
259
+ this._emitCompleteOnItems( 'layout', items );
260
+
261
+ if ( !items || !items.length ) {
262
+ // no items, emit event with empty array
263
+ return;
264
+ }
265
+
266
+ var queue = [];
267
+
268
+ items.forEach( function( item ) {
269
+ // get x/y object from method
270
+ var position = this._getItemLayoutPosition( item );
271
+ // enqueue
272
+ position.item = item;
273
+ position.isInstant = isInstant || item.isLayoutInstant;
274
+ queue.push( position );
275
+ }, this );
276
+
277
+ this._processLayoutQueue( queue );
278
+ };
279
+
280
+ /**
281
+ * get item layout position
282
+ * @param {Outlayer.Item} item
283
+ * @returns {Object} x and y position
284
+ */
285
+ proto._getItemLayoutPosition = function( /* item */ ) {
286
+ return {
287
+ x: 0,
288
+ y: 0
289
+ };
290
+ };
291
+
292
+ /**
293
+ * iterate over array and position each item
294
+ * Reason being - separating this logic prevents 'layout invalidation'
295
+ * thx @paul_irish
296
+ * @param {Array} queue
297
+ */
298
+ proto._processLayoutQueue = function( queue ) {
299
+ this.updateStagger();
300
+ queue.forEach( function( obj, i ) {
301
+ this._positionItem( obj.item, obj.x, obj.y, obj.isInstant, i );
302
+ }, this );
303
+ };
304
+
305
+ // set stagger from option in milliseconds number
306
+ proto.updateStagger = function() {
307
+ var stagger = this.options.stagger;
308
+ if ( stagger === null || stagger === undefined ) {
309
+ this.stagger = 0;
310
+ return;
311
+ }
312
+ this.stagger = getMilliseconds( stagger );
313
+ return this.stagger;
314
+ };
315
+
316
+ /**
317
+ * Sets position of item in DOM
318
+ * @param {Outlayer.Item} item
319
+ * @param {Number} x - horizontal position
320
+ * @param {Number} y - vertical position
321
+ * @param {Boolean} isInstant - disables transitions
322
+ */
323
+ proto._positionItem = function( item, x, y, isInstant, i ) {
324
+ if ( isInstant ) {
325
+ // if not transition, just set CSS
326
+ item.goTo( x, y );
327
+ } else {
328
+ item.stagger( i * this.stagger );
329
+ item.moveTo( x, y );
330
+ }
331
+ };
332
+
333
+ /**
334
+ * Any logic you want to do after each layout,
335
+ * i.e. size the container
336
+ */
337
+ proto._postLayout = function() {
338
+ this.resizeContainer();
339
+ };
340
+
341
+ proto.resizeContainer = function() {
342
+ var isResizingContainer = this._getOption('resizeContainer');
343
+ if ( !isResizingContainer ) {
344
+ return;
345
+ }
346
+ var size = this._getContainerSize();
347
+ if ( size ) {
348
+ this._setContainerMeasure( size.width, true );
349
+ this._setContainerMeasure( size.height, false );
350
+ }
351
+ };
352
+
353
+ /**
354
+ * Sets width or height of container if returned
355
+ * @returns {Object} size
356
+ * @param {Number} width
357
+ * @param {Number} height
358
+ */
359
+ proto._getContainerSize = noop;
360
+
361
+ /**
362
+ * @param {Number} measure - size of width or height
363
+ * @param {Boolean} isWidth
364
+ */
365
+ proto._setContainerMeasure = function( measure, isWidth ) {
366
+ if ( measure === undefined ) {
367
+ return;
368
+ }
369
+
370
+ var elemSize = this.size;
371
+ // add padding and border width if border box
372
+ if ( elemSize.isBorderBox ) {
373
+ measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
374
+ elemSize.borderLeftWidth + elemSize.borderRightWidth :
375
+ elemSize.paddingBottom + elemSize.paddingTop +
376
+ elemSize.borderTopWidth + elemSize.borderBottomWidth;
377
+ }
378
+
379
+ measure = Math.max( measure, 0 );
380
+ this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px';
381
+ };
382
+
383
+ /**
384
+ * emit eventComplete on a collection of items events
385
+ * @param {String} eventName
386
+ * @param {Array} items - Outlayer.Items
387
+ */
388
+ proto._emitCompleteOnItems = function( eventName, items ) {
389
+ var _this = this;
390
+ function onComplete() {
391
+ _this.dispatchEvent( eventName + 'Complete', null, [ items ] );
392
+ }
393
+
394
+ var count = items.length;
395
+ if ( !items || !count ) {
396
+ onComplete();
397
+ return;
398
+ }
399
+
400
+ var doneCount = 0;
401
+ function tick() {
402
+ doneCount++;
403
+ if ( doneCount == count ) {
404
+ onComplete();
405
+ }
406
+ }
407
+
408
+ // bind callback
409
+ items.forEach( function( item ) {
410
+ item.once( eventName, tick );
411
+ });
412
+ };
413
+
414
+ /**
415
+ * emits events via EvEmitter and jQuery events
416
+ * @param {String} type - name of event
417
+ * @param {Event} event - original event
418
+ * @param {Array} args - extra arguments
419
+ */
420
+ proto.dispatchEvent = function( type, event, args ) {
421
+ // add original event to arguments
422
+ var emitArgs = event ? [ event ].concat( args ) : args;
423
+ this.emitEvent( type, emitArgs );
424
+
425
+ if ( jQuery ) {
426
+ // set this.$element
427
+ this.$element = this.$element || jQuery( this.element );
428
+ if ( event ) {
429
+ // create jQuery event
430
+ var $event = jQuery.Event( event );
431
+ $event.type = type;
432
+ this.$element.trigger( $event, args );
433
+ } else {
434
+ // just trigger with type if no event available
435
+ this.$element.trigger( type, args );
436
+ }
437
+ }
438
+ };
439
+
440
+ // -------------------------- ignore & stamps -------------------------- //
441
+
442
+
443
+ /**
444
+ * keep item in collection, but do not lay it out
445
+ * ignored items do not get skipped in layout
446
+ * @param {Element} elem
447
+ */
448
+ proto.ignore = function( elem ) {
449
+ var item = this.getItem( elem );
450
+ if ( item ) {
451
+ item.isIgnored = true;
452
+ }
453
+ };
454
+
455
+ /**
456
+ * return item to layout collection
457
+ * @param {Element} elem
458
+ */
459
+ proto.unignore = function( elem ) {
460
+ var item = this.getItem( elem );
461
+ if ( item ) {
462
+ delete item.isIgnored;
463
+ }
464
+ };
465
+
466
+ /**
467
+ * adds elements to stamps
468
+ * @param {NodeList, Array, Element, or String} elems
469
+ */
470
+ proto.stamp = function( elems ) {
471
+ elems = this._find( elems );
472
+ if ( !elems ) {
473
+ return;
474
+ }
475
+
476
+ this.stamps = this.stamps.concat( elems );
477
+ // ignore
478
+ elems.forEach( this.ignore, this );
479
+ };
480
+
481
+ /**
482
+ * removes elements to stamps
483
+ * @param {NodeList, Array, or Element} elems
484
+ */
485
+ proto.unstamp = function( elems ) {
486
+ elems = this._find( elems );
487
+ if ( !elems ){
488
+ return;
489
+ }
490
+
491
+ elems.forEach( function( elem ) {
492
+ // filter out removed stamp elements
493
+ utils.removeFrom( this.stamps, elem );
494
+ this.unignore( elem );
495
+ }, this );
496
+ };
497
+
498
+ /**
499
+ * finds child elements
500
+ * @param {NodeList, Array, Element, or String} elems
501
+ * @returns {Array} elems
502
+ */
503
+ proto._find = function( elems ) {
504
+ if ( !elems ) {
505
+ return;
506
+ }
507
+ // if string, use argument as selector string
508
+ if ( typeof elems == 'string' ) {
509
+ elems = this.element.querySelectorAll( elems );
510
+ }
511
+ elems = utils.makeArray( elems );
512
+ return elems;
513
+ };
514
+
515
+ proto._manageStamps = function() {
516
+ if ( !this.stamps || !this.stamps.length ) {
517
+ return;
518
+ }
519
+
520
+ this._getBoundingRect();
521
+
522
+ this.stamps.forEach( this._manageStamp, this );
523
+ };
524
+
525
+ // update boundingLeft / Top
526
+ proto._getBoundingRect = function() {
527
+ // get bounding rect for container element
528
+ var boundingRect = this.element.getBoundingClientRect();
529
+ var size = this.size;
530
+ this._boundingRect = {
531
+ left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
532
+ top: boundingRect.top + size.paddingTop + size.borderTopWidth,
533
+ right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
534
+ bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
535
+ };
536
+ };
537
+
538
+ /**
539
+ * @param {Element} stamp
540
+ **/
541
+ proto._manageStamp = noop;
542
+
543
+ /**
544
+ * get x/y position of element relative to container element
545
+ * @param {Element} elem
546
+ * @returns {Object} offset - has left, top, right, bottom
547
+ */
548
+ proto._getElementOffset = function( elem ) {
549
+ var boundingRect = elem.getBoundingClientRect();
550
+ var thisRect = this._boundingRect;
551
+ var size = getSize( elem );
552
+ var offset = {
553
+ left: boundingRect.left - thisRect.left - size.marginLeft,
554
+ top: boundingRect.top - thisRect.top - size.marginTop,
555
+ right: thisRect.right - boundingRect.right - size.marginRight,
556
+ bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
557
+ };
558
+ return offset;
559
+ };
560
+
561
+ // -------------------------- resize -------------------------- //
562
+
563
+ // enable event handlers for listeners
564
+ // i.e. resize -> onresize
565
+ proto.handleEvent = utils.handleEvent;
566
+
567
+ /**
568
+ * Bind layout to window resizing
569
+ */
570
+ proto.bindResize = function() {
571
+ window.addEventListener( 'resize', this );
572
+ this.isResizeBound = true;
573
+ };
574
+
575
+ /**
576
+ * Unbind layout to window resizing
577
+ */
578
+ proto.unbindResize = function() {
579
+ window.removeEventListener( 'resize', this );
580
+ this.isResizeBound = false;
581
+ };
582
+
583
+ proto.onresize = function() {
584
+ this.resize();
585
+ };
586
+
587
+ utils.debounceMethod( Outlayer, 'onresize', 100 );
588
+
589
+ proto.resize = function() {
590
+ // don't trigger if size did not change
591
+ // or if resize was unbound. See #9
592
+ if ( !this.isResizeBound || !this.needsResizeLayout() ) {
593
+ return;
594
+ }
595
+
596
+ this.layout();
597
+ };
598
+
599
+ /**
600
+ * check if layout is needed post layout
601
+ * @returns Boolean
602
+ */
603
+ proto.needsResizeLayout = function() {
604
+ var size = getSize( this.element );
605
+ // check that this.size and size are there
606
+ // IE8 triggers resize on body size change, so they might not be
607
+ var hasSizes = this.size && size;
608
+ return hasSizes && size.innerWidth !== this.size.innerWidth;
609
+ };
610
+
611
+ // -------------------------- methods -------------------------- //
612
+
613
+ /**
614
+ * add items to Outlayer instance
615
+ * @param {Array or NodeList or Element} elems
616
+ * @returns {Array} items - Outlayer.Items
617
+ **/
618
+ proto.addItems = function( elems ) {
619
+ var items = this._itemize( elems );
620
+ // add items to collection
621
+ if ( items.length ) {
622
+ this.items = this.items.concat( items );
623
+ }
624
+ return items;
625
+ };
626
+
627
+ /**
628
+ * Layout newly-appended item elements
629
+ * @param {Array or NodeList or Element} elems
630
+ */
631
+ proto.appended = function( elems ) {
632
+ var items = this.addItems( elems );
633
+ if ( !items.length ) {
634
+ return;
635
+ }
636
+ // layout and reveal just the new items
637
+ this.layoutItems( items, true );
638
+ this.reveal( items );
639
+ };
640
+
641
+ /**
642
+ * Layout prepended elements
643
+ * @param {Array or NodeList or Element} elems
644
+ */
645
+ proto.prepended = function( elems ) {
646
+ var items = this._itemize( elems );
647
+ if ( !items.length ) {
648
+ return;
649
+ }
650
+ // add items to beginning of collection
651
+ var previousItems = this.items.slice(0);
652
+ this.items = items.concat( previousItems );
653
+ // start new layout
654
+ this._resetLayout();
655
+ this._manageStamps();
656
+ // layout new stuff without transition
657
+ this.layoutItems( items, true );
658
+ this.reveal( items );
659
+ // layout previous items
660
+ this.layoutItems( previousItems );
661
+ };
662
+
663
+ /**
664
+ * reveal a collection of items
665
+ * @param {Array of Outlayer.Items} items
666
+ */
667
+ proto.reveal = function( items ) {
668
+ this._emitCompleteOnItems( 'reveal', items );
669
+ if ( !items || !items.length ) {
670
+ return;
671
+ }
672
+ var stagger = this.updateStagger();
673
+ items.forEach( function( item, i ) {
674
+ item.stagger( i * stagger );
675
+ item.reveal();
676
+ });
677
+ };
678
+
679
+ /**
680
+ * hide a collection of items
681
+ * @param {Array of Outlayer.Items} items
682
+ */
683
+ proto.hide = function( items ) {
684
+ this._emitCompleteOnItems( 'hide', items );
685
+ if ( !items || !items.length ) {
686
+ return;
687
+ }
688
+ var stagger = this.updateStagger();
689
+ items.forEach( function( item, i ) {
690
+ item.stagger( i * stagger );
691
+ item.hide();
692
+ });
693
+ };
694
+
695
+ /**
696
+ * reveal item elements
697
+ * @param {Array}, {Element}, {NodeList} items
698
+ */
699
+ proto.revealItemElements = function( elems ) {
700
+ var items = this.getItems( elems );
701
+ this.reveal( items );
702
+ };
703
+
704
+ /**
705
+ * hide item elements
706
+ * @param {Array}, {Element}, {NodeList} items
707
+ */
708
+ proto.hideItemElements = function( elems ) {
709
+ var items = this.getItems( elems );
710
+ this.hide( items );
711
+ };
712
+
713
+ /**
714
+ * get Outlayer.Item, given an Element
715
+ * @param {Element} elem
716
+ * @param {Function} callback
717
+ * @returns {Outlayer.Item} item
718
+ */
719
+ proto.getItem = function( elem ) {
720
+ // loop through items to get the one that matches
721
+ for ( var i=0; i < this.items.length; i++ ) {
722
+ var item = this.items[i];
723
+ if ( item.element == elem ) {
724
+ // return item
725
+ return item;
726
+ }
727
+ }
728
+ };
729
+
730
+ /**
731
+ * get collection of Outlayer.Items, given Elements
732
+ * @param {Array} elems
733
+ * @returns {Array} items - Outlayer.Items
734
+ */
735
+ proto.getItems = function( elems ) {
736
+ elems = utils.makeArray( elems );
737
+ var items = [];
738
+ elems.forEach( function( elem ) {
739
+ var item = this.getItem( elem );
740
+ if ( item ) {
741
+ items.push( item );
742
+ }
743
+ }, this );
744
+
745
+ return items;
746
+ };
747
+
748
+ /**
749
+ * remove element(s) from instance and DOM
750
+ * @param {Array or NodeList or Element} elems
751
+ */
752
+ proto.remove = function( elems ) {
753
+ var removeItems = this.getItems( elems );
754
+
755
+ this._emitCompleteOnItems( 'remove', removeItems );
756
+
757
+ // bail if no items to remove
758
+ if ( !removeItems || !removeItems.length ) {
759
+ return;
760
+ }
761
+
762
+ removeItems.forEach( function( item ) {
763
+ item.remove();
764
+ // remove item from collection
765
+ utils.removeFrom( this.items, item );
766
+ }, this );
767
+ };
768
+
769
+ // ----- destroy ----- //
770
+
771
+ // remove and disable Outlayer instance
772
+ proto.destroy = function() {
773
+ // clean up dynamic styles
774
+ var style = this.element.style;
775
+ style.height = '';
776
+ style.position = '';
777
+ style.width = '';
778
+ // destroy items
779
+ this.items.forEach( function( item ) {
780
+ item.destroy();
781
+ });
782
+
783
+ this.unbindResize();
784
+
785
+ var id = this.element.outlayerGUID;
786
+ delete instances[ id ]; // remove reference to instance by id
787
+ delete this.element.outlayerGUID;
788
+ // remove data for jQuery
789
+ if ( jQuery ) {
790
+ jQuery.removeData( this.element, this.constructor.namespace );
791
+ }
792
+
793
+ };
794
+
795
+ // -------------------------- data -------------------------- //
796
+
797
+ /**
798
+ * get Outlayer instance from element
799
+ * @param {Element} elem
800
+ * @returns {Outlayer}
801
+ */
802
+ Outlayer.data = function( elem ) {
803
+ elem = utils.getQueryElement( elem );
804
+ var id = elem && elem.outlayerGUID;
805
+ return id && instances[ id ];
806
+ };
807
+
808
+
809
+ // -------------------------- create Outlayer class -------------------------- //
810
+
811
+ /**
812
+ * create a layout class
813
+ * @param {String} namespace
814
+ */
815
+ Outlayer.create = function( namespace, options ) {
816
+ // sub-class Outlayer
817
+ var Layout = subclass( Outlayer );
818
+ // apply new options and compatOptions
819
+ Layout.defaults = utils.extend( {}, Outlayer.defaults );
820
+ utils.extend( Layout.defaults, options );
821
+ Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions );
822
+
823
+ Layout.namespace = namespace;
824
+
825
+ Layout.data = Outlayer.data;
826
+
827
+ // sub-class Item
828
+ Layout.Item = subclass( Item );
829
+
830
+ // -------------------------- declarative -------------------------- //
831
+
832
+ utils.htmlInit( Layout, namespace );
833
+
834
+ // -------------------------- jQuery bridge -------------------------- //
835
+
836
+ // make into jQuery plugin
837
+ if ( jQuery && jQuery.bridget ) {
838
+ jQuery.bridget( namespace, Layout );
839
+ }
840
+
841
+ return Layout;
842
+ };
843
+
844
+ function subclass( Parent ) {
845
+ function SubClass() {
846
+ Parent.apply( this, arguments );
847
+ }
848
+
849
+ SubClass.prototype = Object.create( Parent.prototype );
850
+ SubClass.prototype.constructor = SubClass;
851
+
852
+ return SubClass;
853
+ }
854
+
855
+ // ----- helpers ----- //
856
+
857
+ // how many milliseconds are in each unit
858
+ var msUnits = {
859
+ ms: 1,
860
+ s: 1000
861
+ };
862
+
863
+ // munge time-like parameter into millisecond number
864
+ // '0.4s' -> 40
865
+ function getMilliseconds( time ) {
866
+ if ( typeof time == 'number' ) {
867
+ return time;
868
+ }
869
+ var matches = time.match( /(^\d*\.?\d*)(\w*)/ );
870
+ var num = matches && matches[1];
871
+ var unit = matches && matches[2];
872
+ if ( !num.length ) {
873
+ return 0;
874
+ }
875
+ num = parseFloat( num );
876
+ var mult = msUnits[ unit ] || 1;
877
+ return num * mult;
878
+ }
879
+
880
+ // ----- fin ----- //
881
+
882
+ // back in global
883
+ Outlayer.Item = Item;
884
+
885
+ export default Outlayer;