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.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +30 -0
- data/Rakefile +9 -0
- data/app/cells/decidim/gallery/main/image.erb +51 -0
- data/app/cells/decidim/gallery/main/image_collection.erb +13 -0
- data/app/cells/decidim/gallery/main/image_list.erb +9 -0
- data/app/cells/decidim/gallery/main/video.erb +20 -0
- data/app/cells/decidim/gallery/main/video_collection.erb +3 -0
- data/app/cells/decidim/gallery/main_cell.rb +88 -0
- data/app/cells/decidim/gallery/video/show.erb +6 -0
- data/app/cells/decidim/gallery/video_cell.rb +96 -0
- data/app/commands/decidim/gallery/admin/create_gallery_item.rb +50 -0
- data/app/commands/decidim/gallery/admin/publish_gallery_item.rb +38 -0
- data/app/commands/decidim/gallery/admin/unpublish_gallery_item.rb +38 -0
- data/app/commands/decidim/gallery/admin/update_gallery_item.rb +44 -0
- data/app/controllers/decidim/gallery/admin/application_controller.rb +24 -0
- data/app/controllers/decidim/gallery/admin/gallery_item_controller.rb +104 -0
- data/app/controllers/decidim/gallery/application_controller.rb +8 -0
- data/app/controllers/decidim/gallery/gallery_controller.rb +9 -0
- data/app/forms/decidim/gallery/admin/gallery_item_form.rb +17 -0
- data/app/forms/decidim/gallery/admin/gallery_item_image_form.rb +14 -0
- data/app/forms/decidim/gallery/admin/gallery_item_video_form.rb +12 -0
- data/app/helpers/decidim/gallery/admin/application_helper.rb +18 -0
- data/app/model/decidim/gallery/application_record.rb +10 -0
- data/app/model/decidim/gallery/gallery_item.rb +38 -0
- data/app/overrides/decidim/admin/static_pages/edit/add_gallery.html.erb.deface +11 -0
- data/app/overrides/decidim/pages/_standalone/add_content_blocks.html.erb.deface +7 -0
- data/app/overrides/decidim/pages/_tabbed/add_content_blocks.html.erb.deface +5 -0
- data/app/packs/entrypoints/decidim_gallery.js +3 -0
- data/app/packs/images/decidim/gallery/icon.svg +1 -0
- data/app/packs/src/decidim/gallery/gallery.js +42 -0
- data/app/packs/src/decidim/gallery/masonry/EvEmitter.js +85 -0
- data/app/packs/src/decidim/gallery/masonry/getSize.js +181 -0
- data/app/packs/src/decidim/gallery/masonry/jQueryBridget.js +109 -0
- data/app/packs/src/decidim/gallery/masonry/masonry.js +202 -0
- data/app/packs/src/decidim/gallery/masonry/matchesSelector.js +25 -0
- data/app/packs/src/decidim/gallery/masonry/outlayer.js +885 -0
- data/app/packs/src/decidim/gallery/masonry/outlayerItem.js +522 -0
- data/app/packs/src/decidim/gallery/masonry/utils.js +203 -0
- data/app/packs/stylesheets/decidim/gallery/_gallery.scss +3 -0
- data/app/permissions/decidim/gallery/admin/permissions.rb +18 -0
- data/app/permissions/decidim/gallery/permissions.rb +17 -0
- data/app/views/decidim/gallery/admin/gallery_item/_form_image.html.erb +16 -0
- data/app/views/decidim/gallery/admin/gallery_item/_form_video.html.erb +16 -0
- data/app/views/decidim/gallery/admin/gallery_item/edit.html.erb +9 -0
- data/app/views/decidim/gallery/admin/gallery_item/index.html.erb +52 -0
- data/app/views/decidim/gallery/admin/gallery_item/new.html.erb +9 -0
- data/app/views/decidim/gallery/gallery/index.html.erb +11 -0
- data/config/assets.rb +9 -0
- data/config/i18n-tasks.yml +22 -0
- data/config/locales/en.yml +79 -0
- data/config/locales/fr.yml +79 -0
- data/config/locales/ro.yml +79 -0
- data/lib/decidim/gallery/admin/static_pages/command.rb +15 -0
- data/lib/decidim/gallery/admin/static_pages/form.rb +21 -0
- data/lib/decidim/gallery/admin.rb +14 -0
- data/lib/decidim/gallery/admin_engine.rb +27 -0
- data/lib/decidim/gallery/component.rb +47 -0
- data/lib/decidim/gallery/engine.rb +60 -0
- data/lib/decidim/gallery/test/factories.rb +58 -0
- data/lib/decidim/gallery/version.rb +14 -0
- data/lib/decidim/gallery.rb +14 -0
- data/lib/tasks/decidim_gallery.rake +16 -0
- 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;
|