├── .gitignore
├── AssetBundle.php
├── Module.php
├── README.md
├── assets
├── css
│ ├── style.css
│ └── style.less
├── images
│ └── preload.gif
└── js
│ ├── fancybox
│ ├── .gitattributes
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── demo
│ │ ├── 1_b.jpg
│ │ ├── 1_s.jpg
│ │ ├── 2_b.jpg
│ │ ├── 2_s.jpg
│ │ ├── 3_b.jpg
│ │ ├── 3_s.jpg
│ │ ├── 4_b.jpg
│ │ ├── 4_s.jpg
│ │ ├── 5_b.jpg
│ │ ├── 5_s.jpg
│ │ ├── ajax.txt
│ │ ├── iframe.html
│ │ └── index.html
│ ├── lib
│ │ ├── jquery-1.10.1.min.js
│ │ ├── jquery-1.9.0.min.js
│ │ └── jquery.mousewheel-3.0.6.pack.js
│ ├── source
│ │ ├── blank.gif
│ │ ├── fancybox_loading.gif
│ │ ├── fancybox_loading@2x.gif
│ │ ├── fancybox_overlay.png
│ │ ├── fancybox_sprite.png
│ │ ├── fancybox_sprite@2x.png
│ │ ├── helpers
│ │ │ ├── fancybox_buttons.png
│ │ │ ├── jquery.fancybox-buttons.css
│ │ │ ├── jquery.fancybox-buttons.js
│ │ │ ├── jquery.fancybox-media.js
│ │ │ ├── jquery.fancybox-thumbs.css
│ │ │ └── jquery.fancybox-thumbs.js
│ │ ├── jquery.fancybox.css
│ │ ├── jquery.fancybox.js
│ │ └── jquery.fancybox.pack.js
│ └── sprite.psd
│ └── sortable
│ ├── .editorconfig
│ ├── .gitignore
│ ├── .jshintrc
│ ├── CONTRIBUTING.md
│ ├── Gruntfile.js
│ ├── README.md
│ ├── Sortable.js
│ ├── Sortable.min.js
│ ├── bower.json
│ ├── component.json
│ ├── index.html
│ ├── jquery.binding.js
│ ├── knockout-sortable.js
│ ├── meteor
│ ├── README.md
│ ├── example
│ │ ├── .meteor
│ │ │ ├── .gitignore
│ │ │ ├── packages
│ │ │ ├── release
│ │ │ └── versions
│ │ ├── README.md
│ │ ├── client
│ │ │ ├── define-object-type.css
│ │ │ ├── define-object-type.html
│ │ │ └── define-object-type.js
│ │ ├── model.js
│ │ ├── package.json
│ │ ├── packages
│ │ │ └── Sortable
│ │ ├── run.bat
│ │ ├── run.sh
│ │ └── server
│ │ │ └── fixtures.js
│ ├── methods.js
│ ├── package.js
│ ├── publish.sh
│ ├── reactivize.js
│ ├── runtests.sh
│ ├── template.html
│ └── test.js
│ ├── ng-sortable.js
│ ├── package.json
│ ├── react-sortable-mixin.js
│ └── st
│ ├── app.css
│ ├── app.js
│ ├── face-01.jpg
│ ├── face-02.jpg
│ ├── face-03.jpg
│ ├── face-04.jpg
│ ├── face-05.jpg
│ ├── face-06.jpg
│ ├── face-07.jpg
│ ├── face-08.jpg
│ ├── face-09.jpg
│ ├── iframe
│ ├── frame.html
│ └── index.html
│ ├── logo.png
│ └── og-image.png
├── composer.json
├── controllers
└── GalleryController.php
├── messages
├── en
│ └── default.php
├── ru
│ └── default.php
└── uk
│ └── default.php
├── migrations
└── m150407_084217_gallery.php
├── models
├── Gallery.php
├── GalleryFile.php
├── GallerySearch.php
└── Query.php
├── views
└── gallery
│ ├── _form.php
│ ├── _image.php
│ ├── create.php
│ ├── index.php
│ ├── update.php
│ └── view.php
└── widgets
└── Gallery.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .git
2 |
--------------------------------------------------------------------------------
/AssetBundle.php:
--------------------------------------------------------------------------------
1 | sourcePath = __DIR__ . '/assets';
37 |
38 | parent::init();
39 | }
40 | }
--------------------------------------------------------------------------------
/Module.php:
--------------------------------------------------------------------------------
1 | registerTranslations();
41 | }
42 |
43 | public function registerTranslations()
44 | {
45 | Yii::$app->i18n->translations['sadovojav/gallery/*'] = [
46 | 'class' => 'yii\i18n\PhpMessageSource',
47 | 'sourceLanguage' => 'en-US',
48 | 'basePath' => '@vendor/sadovojav/yii2-gallery-module/messages',
49 | 'fileMap' => [
50 | 'sadovojav/gallery/default' => 'default.php',
51 | ],
52 | ];
53 | }
54 |
55 | public static function t($category, $message, $params = [], $language = null)
56 | {
57 | return Yii::t('sadovojav/gallery/' . $category, $message, $params, $language);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Yii2 gallery
2 |
3 | #### Features:
4 | - Upload image
5 | - Drag image and change position
6 | - Make gallery template
7 | - Use inline widget
8 |
9 | 
10 |
11 | ## Installation
12 |
13 | ### Composer
14 |
15 | The preferred way to install this extension is through [Composer](http://getcomposer.org/).
16 |
17 | Either run ```php composer.phar require sadovojav/yii2-gallery-module "dev-master"```
18 |
19 | or add ```"sadovojav/yii2-gallery-module": "dev-master"``` to the require section of your ```composer.json```
20 |
21 | ### Migration
22 |
23 | yii migrate --migrationPath=@vendor/sadovojav/yii2-gallery-module/migrations
24 |
25 | ## Config
26 |
27 | 1. Attach the module in your config file:
28 |
29 | ```php
30 | 'modules' => [
31 | 'gallery' => [
32 | 'class' => 'sadovojav\gallery\Module',
33 | ],
34 | ],
35 | ```
36 | - string `basePath` = `@webroot/galleries` - Base path
37 | - integer `queryCacheDuration` = `86400` - Query cache duration
38 | - bool `uniqueName` = `false` - Generate unique name
39 |
40 | 2. If you want use custom template, you can set path map
41 |
42 | ```php
43 | 'view' => [
44 | 'theme' => [
45 | 'pathMap' => [
46 | '@sadovojav/gallery/widgets/views' => '@app/web/views/gallery'
47 | ],
48 | 'baseUrl' => '@web/web'
49 | ],
50 | ],
51 | ```
52 |
53 | ## Administration
54 |
55 | Galleries manager - **/gallery/gallery/index**
56 |
57 | ## Using
58 |
59 | #### 1. Widget
60 |
61 | ~~~
62 | = \sadovojav\gallery\widgets\Gallery::widget([
63 | 'galleryId' => $model->galleryId
64 | ]); ?>
65 | ~~~
66 |
67 | - integer `galleryId` required - Gallery Id
68 | - bool `caption` = `false` - Show caption in default template
69 | - string `template` = `null` - Your custom widget template
70 |
71 |
72 | #### 2. Inline Widget
73 |
74 | See here -> [https://github.com/sadovojav/yii2-inline-widgets-behavior](https://github.com/sadovojav/yii2-inline-widgets-behavior)
75 |
--------------------------------------------------------------------------------
/assets/css/style.css:
--------------------------------------------------------------------------------
1 | .modal-open {
2 | padding: 0 !important;
3 | overflow: inherit !important;
4 | }
5 | .image {
6 | margin-top: 15px;
7 | margin-bottom: 15px;
8 | }
9 | .image.sortable-ghost {
10 | opacity: 0.6;
11 | }
12 | .image.preload {
13 | background-image: url("../images/preload.gif");
14 | background-position: center bottom 10px;
15 | background-repeat: no-repeat;
16 | opacity: 0.6;
17 | }
18 | .image .wrapper {
19 | border: 1px solid #ddd;
20 | }
21 | .image .wrapper .handle {
22 | height: 175px;
23 | overflow: hidden;
24 | position: relative;
25 | vertical-align: middle;
26 | cursor: move;
27 | }
28 | .image .wrapper .handle img {
29 | position: absolute;
30 | left: 50%;
31 | top: 50%;
32 | height: 100%;
33 | width: auto;
34 | -webkit-transform: translate(-50%, -50%);
35 | -ms-transform: translate(-50%, -50%);
36 | transform: translate(-50%, -50%);
37 | }
38 | .image .wrapper .handle img.portrait {
39 | width: 100%;
40 | height: auto;
41 | }
42 | .image .wrapper .bottom {
43 | padding: 5px;
44 | }
45 | .image .wrapper .bottom .actions {
46 | text-align: right;
47 | padding-top: 3px;
48 | }
49 | .image .wrapper .bottom .actions > button {
50 | background: none;
51 | border: 0;
52 | }
53 | .image .wrapper .bottom .actions i {
54 | cursor: pointer;
55 | padding: 1px 5px;
56 | font-size: 12px;
57 | line-height: 1.5;
58 | border-radius: 3px;
59 | border: 1px solid #ccc;
60 | }
61 | .image .wrapper .bottom .actions i:hover {
62 | background: #e6e6e6;
63 | }
64 | .image .wrapper .bottom .actions .watch {
65 | color: #000;
66 | }
67 | .image .wrapper .bottom .actions .edit {
68 | color: #31708f;
69 | }
70 | .image .wrapper .bottom .actions .remove {
71 | color: #a94442;
72 | }
73 | .file-preview-frame {
74 | padding: 0 !important;
75 | box-shadow: none !important;
76 | }
77 | .file-preview-frame:hover {
78 | box-shadow: none !important;
79 | }
80 | .file-preview-frame .file-upload-indicator {
81 | height: 23px;
82 | }
83 | .file-preview-frame .file-upload-indicator:hover {
84 | padding-top: 2px !important;
85 | font-size: 1em !important;
86 | }
87 | .file-preview-frame .file-caption-name {
88 | display: none;
89 | }
90 | .file-preview-frame .file-actions {
91 | padding: 5px;
92 | }
93 |
--------------------------------------------------------------------------------
/assets/css/style.less:
--------------------------------------------------------------------------------
1 | .modal-open {
2 | padding: 0 !important;
3 | overflow: inherit !important;
4 | }
5 |
6 | .image {
7 | margin-top: 15px;
8 | margin-bottom: 15px;
9 | &.sortable-ghost {
10 | opacity: 0.6;
11 | }
12 | &.preload {
13 | background-image: url("../images/preload.gif");
14 | background-position: center bottom 10px;
15 | background-repeat: no-repeat;
16 | opacity: 0.6;
17 | }
18 | .wrapper {
19 | border: 1px solid #ddd;
20 | .handle {
21 | height: 175px;
22 | overflow: hidden;
23 | position: relative;
24 | vertical-align: middle;
25 | cursor: move;
26 | img {
27 | position: absolute;
28 | left: 50%;
29 | top: 50%;
30 | height: 100%;
31 | width: auto;
32 | -webkit-transform: translate(-50%, -50%);
33 | -ms-transform: translate(-50%, -50%);
34 | transform: translate(-50%, -50%);
35 | &.portrait {
36 | width: 100%;
37 | height: auto;
38 | }
39 | }
40 | }
41 | .bottom {
42 | padding: 5px;
43 | .actions {
44 | text-align: right;
45 | padding-top: 3px;
46 | > button {
47 | background: none;
48 | border: 0;
49 | }
50 | i {
51 | cursor: pointer;
52 | padding: 1px 5px;
53 | font-size: 12px;
54 | line-height: 1.5;
55 | border-radius: 3px;
56 | border: 1px solid #ccc;
57 | &:hover {
58 | background: #e6e6e6;
59 | }
60 | }
61 | .watch {
62 | color: #000;
63 | }
64 | .edit {
65 | color: #31708f;
66 | }
67 | .remove {
68 | color: #a94442;
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | .file-preview-frame {
76 | padding: 0 !important;
77 | box-shadow: none !important;
78 | &:hover {
79 | box-shadow: none !important;
80 | }
81 | .file-upload-indicator {
82 | height: 23px;
83 | &:hover {
84 | padding-top: 2px !important;
85 | font-size: 1em !important;
86 | }
87 | }
88 | .file-caption-name {
89 | display: none;
90 | }
91 | .file-actions {
92 | padding: 5px;
93 | }
94 | }
--------------------------------------------------------------------------------
/assets/images/preload.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/images/preload.gif
--------------------------------------------------------------------------------
/assets/js/fancybox/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Denote all files that are truly binary and should not be modified.
5 | *.png binary
6 | *.jpg binary
7 | *.gif binary
--------------------------------------------------------------------------------
/assets/js/fancybox/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | fancyBox - Changelog
2 | =========
3 |
4 | ### Version 2.1.5 - June 14, 2013
5 | * Fixed #493 - Broken slideshow
6 | * Fixed #556 - Parent option
7 | * Retina graphics (#564) and retina display support (#420)
8 | * Improved "lock" feature
9 |
10 | ### Version 2.1.4 - January 10, 2013
11 | * Update to be compatible with jQuery v1.9
12 | * Small changes that should fix usability issues for certain users
13 |
14 | ### Version 2.1.3 - October 23, 2012
15 |
16 | * Fixed #426 - Broken IE7
17 | * Fixed #423 - Background flickering on iOS
18 | * Fixed #418 - Automatically Grow/Shrink and Center
19 | * Updated the script to work with jQuery 1.6
20 | * Media helper supports YouTube video series
21 |
22 | ### Version 2.1.2 - October 15, 2012
23 |
24 | * Fixed #414 - Don't allow nextClick if there is only one item
25 | * Fixed #397 - Button helper 'Menu' not visible in IE7
26 | * Overlay can be opened/closed manually:
27 | * $.fancybox.helpers.overlay.open();
28 | * $.fancybox.helpers.overlay.open({closeClick : false});
29 | * $.fancybox.helpers.overlay.close();
30 | * Optimized for Internet Explorer 10 (Windows 8)
31 |
32 | ### Version 2.1.1 - October 01, 2012
33 |
34 | * Fixed #357 - Converting values like 'auto' in getScalar()
35 | * Fixed #358 - Updated overlay background image
36 | * New "fancybox-href" and "fancybox-title" HTML5 data-attributes (#317)
37 | * Improved helpers:
38 | * - now they can have a property 'defaults' that contains default settings
39 | * - updated vimeo and youtube parsers for media helper
40 | * Content locking now can be turned off
41 |
42 | ### Version 2.1.0 - August 20, 2012
43 |
44 | * Fixed #103 - DOM element re-injection after closing
45 | * Fixed #188 - navigation keys inside editable content
46 | * New animation directions (see https://github.com/fancyapps/fancyBox/issues/233#issuecomment-5512453)
47 | * New option "iframe" - it is now possible to separate scrolling for iframe and wrapping element; choose to preload
48 | * New option "swf" - brings back functionality from fancyBox v1
49 | * Improved media helper - better support for vimeo and youtube; links are now configurable
50 | * Rewritten overlay helper:
51 | * - new option "showEarly" - toggles if should be open before of after content is loaded
52 | * - Facebook-style (https://github.com/fancyapps/fancyBox/issues/24) and therefore uses image for background
53 | * Option "padding" accepts array (e.g., padding: [15, 50, 10, 5])
54 | * One of dimensions (width or height) can now be set to "auto" (option "autoSize" needs to be "false")
55 | * Updated callbacks:
56 | * - "beforeClose" is now called only once
57 | * - "afterLoad" receives current and previous object as arguments
58 | * Method "$.fancybox.update();" recalculates content width/height
59 | * Updated to work with jQuery v1.8
60 |
61 | ### Version 2.0.6 - April 16, 2012
62 |
63 | * Fixed #188 - keystrokes in contenteditable
64 | * Fixed #171 - non-images should not be preloaded
65 | * Fixed #158 - 'closeClick: true' breaks gallery navigation
66 | * New "media" helper - detects and displays various media types
67 | * New option "groupAttr" - name of group selector attribute, default is "data-fancybox-group"
68 | * New feature - selector expressions in URLs, see #170
69 | * Improved 'overlay' helper to use "position: fixed"
70 | * Improved autoSize, fixed wrong height in some cases
71 | * Improved centering and iframe scrolling for iOS
72 | * Updated markup, new element '.fancybox-skin' is now used for styling
73 |
74 | ### Version 2.0.5 - February 21, 2012
75 |
76 | * Fixed #155 - easing for prev/next animations
77 | * Fixed #153 - overriding "keys" options
78 | * Fixed #147 - IE7 problem with #hash links
79 | * Fixed #130 - changing dynamically data-fancybox-group
80 | * Fixed #126 - obey minWidth/minHeight
81 | * Fixed #118 - placement of loading icon and navigation arrows
82 | * Fixed #101 - "index" option not working
83 | * Fixed #94 - "orig" option not working
84 | * Fixed #80 - does not work on IE6
85 | * Fixed #72 - can't set overlay opacity to 0
86 | * Fixed #63 - properly set gallery index
87 | * New option "autoCenter" - toggles centering on window resize or scroll, disabled for mobile devices by default
88 | * New option "autoResize" - toggles responsivity, disabled for mobile devices by default
89 | * New option "preload" - number of images to preload
90 | * New feature to target mobile/desktop browsers using CSS, see #108
91 | * Changed ajax option defaults to "{ dataType: 'html', headers: { 'X-fancyBox': true } }", see #150 and #128
92 | * Updated loading icon for IE7, IE8
93 | * Calculates height of the iframe if 'autoSize' is set to 'true' and the iframe is on the same domain as the main page
94 |
95 | ### Version 2.0.4 - December 12, 2011
96 |
97 | * Fixed #47 - fix overriding properties
98 | * New option "position" to thumbnail and button helpers
99 |
100 |
101 | ### Version 2.0.3 - November 29, 2011
102 |
103 | * Fixed #29 - broken elastic transitions
104 |
105 |
106 | ### Version 2.0.2 - November 28, 2011
107 |
108 | * Fixed slideshow
109 | * Fixed scrollbars issue when displayed a very tall image
110 | * New option "nextClick" - navigate to next gallery item when user clicks the content
111 | * New option "modal" - to disable navigation and closing
112 | * Add 'metadata' plugin support
113 | * Add ability to create groups using 'data-fancybox-group' attribute
114 | * Updated manual usage to match earlier releases
115 |
116 |
117 | ### Version 2.0.1 - November 23, 2011
118 |
119 | * Fixed keyboard events inside form elements
120 | * Fixed manual usage
121 |
122 |
123 | ### Version 2.0.0 - November 21, 2011
124 |
125 | First release - completely rewritten, many new features and updated graphics.
--------------------------------------------------------------------------------
/assets/js/fancybox/README.md:
--------------------------------------------------------------------------------
1 | fancyBox
2 | ========
3 |
4 | fancyBox is a tool that offers a nice and elegant way to add zooming functionality for images, html content and multi-media on your webpages.
5 |
6 | More information and examples: http://www.fancyapps.com/fancybox/
7 |
8 | License: http://www.fancyapps.com/fancybox/#license
9 |
10 | Copyright (c) 2012 Janis Skarnelis - janis@fancyapps.com
11 |
12 |
13 | How to use
14 | ----------
15 |
16 | To get started, download the plugin, unzip it and copy files to your website/application directory.
17 | Load files in the
section of your HTML document. Make sure you also add the jQuery library.
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Create your links with a `title` if you want a title to be shown, and add a class:
26 |
27 |
28 |
29 | If you have a set of related items that you would like to group,
30 | additionally include a group name in the `rel` (or `data-fancybox-group`) attribute:
31 |
32 |
33 |
34 |
35 | Initialise the script like this:
36 |
37 |
42 |
43 | May also be passed an optional options object which will extend the default values. Example:
44 |
45 |
53 |
54 | Tip: Automatically group and apply fancyBox to all images:
55 |
56 | $("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.png'],a[href$='.gif']").attr('rel', 'gallery').fancybox();
57 |
58 | Script uses the `href` attribute of the matched elements to obtain the location of the content and to figure out content type you want to display.
59 | You can specify type directly by adding classname (fancybox.image, fancybox.iframe, etc) or `data-fancybox-type` attribute:
60 |
61 | //Ajax:
62 | Example
63 | //or
64 | Example
65 |
66 | //Iframe:
67 | Example
68 |
69 | //Inline (will display an element with `id="example"`)
70 | Example
71 |
72 | //SWF:
73 | Example
74 |
75 | //Image:
76 | Example
77 |
78 | Note, ajax requests are subject to the [same origin policy](http://en.wikipedia.org/wiki/Same_origin_policy).
79 | If fancyBox will not be able to get content type, it will try to guess based on 'href' and will quit silently if would not succeed.
80 | (this is different from previsous versions where 'ajax' was used as default type or an error message was displayed).
81 |
82 | Advanced
83 | --------
84 |
85 | ### Helpers
86 |
87 | Helpers provide a simple mechanism to extend the capabilities of fancyBox. There are two built-in helpers - 'overlay' and 'title'.
88 | You can disable them, set custom options or enable other helpers. Examples:
89 |
90 | //Disable title helper
91 | $(".fancybox").fancybox({
92 | helpers: {
93 | title: null
94 | }
95 | });
96 |
97 | //Disable overlay helper
98 | $(".fancybox").fancybox({
99 | helpers: {
100 | overlay : null
101 | }
102 | });
103 |
104 | //Change title position and overlay color
105 | $(".fancybox").fancybox({
106 | helpers: {
107 | title : {
108 | type : 'inside'
109 | },
110 | overlay : {
111 | css : {
112 | 'background' : 'rgba(255,255,255,0.5)'
113 | }
114 | }
115 | }
116 | });
117 |
118 | //Enable thumbnail helper and set custom options
119 | $(".fancybox").fancybox({
120 | helpers: {
121 | thumbs : {
122 | width: 50,
123 | height: 50
124 | }
125 | }
126 | });
127 |
128 |
129 | ### API
130 |
131 | Also available are event driven callback methods. The `this` keyword refers to the current or upcoming object (depends on callback method). Here is how you can change title:
132 |
133 | $(".fancybox").fancybox({
134 | beforeLoad : function() {
135 | this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
136 |
137 | /*
138 | "this.element" refers to current element, so you can, for example, use the "alt" attribute of the image to store the title:
139 | this.title = $(this.element).find('img').attr('alt');
140 | */
141 | }
142 | });
143 |
144 | It`s possible to open fancyBox programmatically in various ways:
145 |
146 | //HTML content:
147 | $.fancybox( '
Lorem Lipsum
Lorem lipsum
', {
148 | title : 'Custom Title'
149 | });
150 |
151 | //DOM element:
152 | $.fancybox( $("#inline"), {
153 | title : 'Custom Title'
154 | });
155 |
156 | //Custom object:
157 | $.fancybox({
158 | href: 'example.jpg',
159 | title : 'Custom Title'
160 | });
161 |
162 | //Array of objects:
163 | $.fancybox([
164 | {
165 | href: 'example1.jpg',
166 | title : 'Custom Title 1'
167 | },
168 | {
169 | href: 'example2.jpg',
170 | title : 'Custom Title 2'
171 | }
172 | ], {
173 | padding: 0
174 | });
175 |
176 | There are several methods that allow you to interact with and manipulate fancyBox, example:
177 |
178 | //Close fancybox:
179 | $.fancybox.close();
180 |
181 | There is a simply way to access wrapping elements using JS:
182 |
183 | $.fancybox.wrap
184 | $.fancybox.skin
185 | $.fancybox.outer
186 | $.fancybox.inner
187 |
188 | You can override CSS to customize the look. For example, make navigation arrows always visible,
189 | change width and move them outside of area (use this snippet after including fancybox.css):
190 |
191 | .fancybox-nav span {
192 | visibility: visible;
193 | }
194 |
195 | .fancybox-nav {
196 | width: 80px;
197 | }
198 |
199 | .fancybox-prev {
200 | left: -80px;
201 | }
202 |
203 | .fancybox-next {
204 | right: -80px;
205 | }
206 |
207 | In that case, you might want to increase space around box:
208 |
209 | $(".fancybox").fancybox({
210 | margin : [20, 60, 20, 60]
211 | });
212 |
213 |
214 | Bug tracker
215 | -----------
216 |
217 | Have a bug? Please create an issue on GitHub at https://github.com/fancyapps/fancyBox/issues
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/1_b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/1_b.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/1_s.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/1_s.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/2_b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/2_b.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/2_s.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/2_s.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/3_b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/3_b.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/3_s.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/3_s.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/4_b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/4_b.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/4_s.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/4_s.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/5_b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/5_b.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/5_s.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadovyiov/yii2-gallery-module/91eff13efd40a07d43d8fa90c6fe059d40de0aae/assets/js/fancybox/demo/5_s.jpg
--------------------------------------------------------------------------------
/assets/js/fancybox/demo/ajax.txt:
--------------------------------------------------------------------------------
1 |
7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas fermentum ante et sapien dignissim in viverra magna feugiat. Donec tempus ipsum nec neque dignissim quis eleifend eros gravida. Praesent nisi massa, sodales quis tincidunt ac, semper quis risus. In suscipit nisl sed leo aliquet consequat. Integer vitae augue in risus porttitor pellentesque eu eget odio. Fusce ut sagittis quam. Morbi aliquam interdum blandit. Integer pharetra tempor velit, aliquam dictum justo tempus sed. Morbi congue fringilla justo a feugiat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent quis metus et nisl consectetur pharetra. Nam bibendum turpis eu metus luctus eu volutpat sem molestie. Nam sollicitudin porttitor lorem, ac ultricies est venenatis eu. Ut dignissim elit et orci feugiat ac placerat purus euismod. Ut mi lorem, cursus et sagittis elementum, luctus ac massa.
8 |
9 |
10 | Phasellus et ligula vel diam ullamcorper volutpat. Integer rhoncus rhoncus aliquam. Aliquam erat volutpat. Aenean luctus vestibulum placerat. Quisque quam neque, lacinia aliquet eleifend ac, aliquet blandit felis. Curabitur porta ultricies dui, sit amet mattis quam euismod a. Ut eleifend scelerisque neque, sit amet accumsan odio consequat ut. Proin facilisis auctor elit sed accumsan. Cras dapibus nisl in nisi rhoncus laoreet. Nullam pellentesque tortor libero, eget facilisis ipsum. Donec ultricies tellus tellus, in tincidunt purus. Nullam in est aliquam velit scelerisque blandit. In tincidunt, magna a dapibus imperdiet, quam urna elementum leo, vitae rhoncus nisl velit cursus velit. In dignissim sem ac mauris rhoncus ornare.
11 |
12 |
13 | Duis imperdiet velit vel quam malesuada suscipit imperdiet tellus hendrerit. Mauris vestibulum odio mauris, ut placerat leo. Mauris quis neque at tellus feugiat congue id non enim. Nam vehicula posuere nulla eget vehicula. Donec pretium purus nec ligula porta eu laoreet sapien venenatis. Nulla facilisi. Phasellus eget mi enim. Phasellus molestie tincidunt ultrices. Aenean id sem a tellus lobortis tincidunt. Nam laoreet nulla vel velit tincidunt ac rutrum libero malesuada. Nulla consequat dolor quis nisl tempor fermentum. Integer sodales pretium varius. Aenean a leo vitae odio dictum dignissim malesuada nec dolor. Phasellus adipiscing viverra est, ac sagittis libero sagittis quis. Sed interdum dapibus nunc et fringilla. Nunc vel velit et urna laoreet bibendum.
14 |
19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam scelerisque justo ac eros consectetur bibendum. In hac habitasse platea dictumst. Nulla aliquam turpis et tellus elementum luctus. Duis sit amet rhoncus velit. Duis nisl ligula, mattis interdum blandit laoreet, mattis id ante. Cras pulvinar lacus vitae nisi egestas non euismod neque bibendum. Vestibulum faucibus libero id ante molestie ultricies. Vestibulum quis nibh felis. Vestibulum libero nisl, vehicula vel ullamcorper sit amet, tristique sit amet augue. Etiam urna neque, porttitor sed sodales lacinia, posuere a nisl. Vestibulum blandit neque in sapien volutpat ac condimentum sapien auctor. Ut imperdiet venenatis ultricies. Phasellus accumsan, sem eu placerat commodo, felis purus commodo ipsum, sit amet vulputate orci est viverra est.
20 |
21 |
22 |
23 | Aenean velit est, condimentum ut iaculis ut, accumsan at mi. Maecenas velit mi, venenatis ut condimentum at, ultrices vel tortor. Curabitur pharetra ornare dapibus. Ut volutpat cursus semper. In hac habitasse platea dictumst. Donec eu iaculis ipsum. Morbi eu dolor velit, a semper nunc.
24 |
244 | fancyBox will try to guess content type from href attribute but you can specify it directly by adding classname (fancybox.image, fancybox.iframe, etc).
245 |
256 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam quis mi eu elit tempor facilisis id et neque. Nulla sit amet sem sapien. Vestibulum imperdiet porta ante ac ornare. Nulla et lorem eu nibh adipiscing ultricies nec at lacus. Cras laoreet ultricies sem, at blandit mi eleifend aliquam. Nunc enim ipsum, vehicula non pretium varius, cursus ac tortor. Vivamus fringilla congue laoreet. Quisque ultrices sodales orci, quis rhoncus justo auctor in. Phasellus dui eros, bibendum eu feugiat ornare, faucibus eu mi. Nunc aliquet tempus sem, id aliquam diam varius ac. Maecenas nisl nunc, molestie vitae eleifend vel, iaculis sed magna. Aenean tempus lacus vitae orci posuere porttitor eget non felis. Donec lectus elit, aliquam nec eleifend sit amet, vestibulum sed nunc.
257 |
258 |
259 |
260 |
261 | Ajax example will not run from your local computer and requires a server to run.
262 |
263 |
264 |
Button helper
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
Thumbnail helper
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
Media helper
287 |
288 | Will not run from your local computer, requires a server to run.
289 |
198 | {{remaining()}} of {{todos.length}} remaining
199 |
200 |
201 |
202 | {{todo.text}}
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
Code example
218 |
// Simple list
219 | var list = document.getElementById("my-ui-list");
220 | Sortable.create(list); // That's all.
221 |
222 |
223 | // Grouping
224 | var foo = document.getElementById("foo");
225 | Sortable.create(foo, { group: "omega" });
226 |
227 | var bar = document.getElementById("bar");
228 | Sortable.create(bar, { group: "omega" });
229 |
230 |
231 | // Or
232 | var container = document.getElementById("multi");
233 | var sort = Sortable.create(container, {
234 | animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
235 | handle: ".tile__title", // Restricts sort start click/touch to the specified element
236 | draggable: ".tile", // Specifies which items inside the element should be sortable
237 | onUpdate: function (evt/**Event*/){
238 | var item = evt.item; // the current dragged HTMLElement
239 | }
240 | });
241 |
242 | // ..
243 | sort.destroy();
244 |
245 |
246 | // Editable list
247 | var editableList = Sortable.create(editable, {
248 | filter: '.js-remove',
249 | onFilter: function (evt) {
250 | var el = editableList.closest(evt.item); // get dragged item
251 | el && el.parentNode.removeChild(el);
252 | }
253 | });
254 |
255 |
256 |
257 |
258 |
259 |
See also
260 |
Loading…
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
330 |
331 |
332 |
333 |
334 |
343 |
344 |
345 |
--------------------------------------------------------------------------------
/assets/js/sortable/jquery.binding.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jQuery plugin for Sortable
3 | * @author RubaXa
4 | * @license MIT
5 | */
6 | (function (factory) {
7 | "use strict";
8 |
9 | if (typeof define === "function" && define.amd) {
10 | define(["jquery"], factory);
11 | }
12 | else {
13 | /* jshint sub:true */
14 | factory(jQuery);
15 | }
16 | })(function ($) {
17 | "use strict";
18 |
19 |
20 | /* CODE */
21 |
22 |
23 | /**
24 | * jQuery plugin for Sortable
25 | * @param {Object|String} options
26 | * @param {..*} [args]
27 | * @returns {jQuery|*}
28 | */
29 | $.fn.sortable = function (options) {
30 | var retVal;
31 |
32 | this.each(function () {
33 | var $el = $(this),
34 | sortable = $el.data('sortable');
35 |
36 | if (!sortable && (options instanceof Object || !options)) {
37 | sortable = new Sortable(this, options);
38 | $el.data('sortable', sortable);
39 | }
40 |
41 | if (sortable) {
42 | if (options === 'widget') {
43 | return sortable;
44 | }
45 | else if (options === 'destroy') {
46 | sortable.destroy();
47 | $el.removeData('sortable');
48 | }
49 | else if (options in sortable) {
50 | retVal = sortable[sortable].apply(sortable, [].slice.call(arguments, 1));
51 | }
52 | }
53 | });
54 |
55 | return (retVal === void 0) ? this : retVal;
56 | };
57 | });
58 |
--------------------------------------------------------------------------------
/assets/js/sortable/knockout-sortable.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | "use strict";
3 |
4 | var init = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) {
5 |
6 | var options = buildOptions(valueAccessor, sortableOptions);
7 |
8 | //It's seems that we cannot update the eventhandlers after we've created the sortable, so define them in init instead of update
9 | ['onStart', 'onEnd', 'onRemove', 'onAdd', 'onUpdate', 'onSort', 'onFilter'].forEach(function (e) {
10 | if (options[e] || eventHandlers[e])
11 | options[e] = function (eventType, parentVM, parentBindings, handler, e) {
12 | var itemVM = ko.dataFor(e.item),
13 | //All of the bindings on the parent element
14 | bindings = ko.utils.peekObservable(parentBindings()),
15 | //The binding options for the draggable/sortable binding of the parent element
16 | bindingHandlerBinding = bindings.sortable || bindings.draggable,
17 | //The collection that we should modify
18 | collection = bindingHandlerBinding.collection || bindingHandlerBinding.foreach;
19 | if (handler)
20 | handler(e, itemVM, parentVM, collection, bindings);
21 | if (eventHandlers[eventType])
22 | eventHandlers[eventType](e, itemVM, parentVM, collection, bindings);
23 | }.bind(undefined, e, viewModel, allBindings, options[e]);
24 | });
25 |
26 | viewModel._sortable = Sortable.create(element, options);
27 |
28 | //Destroy the sortable if knockout disposes the element it's connected to
29 | ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
30 | viewModel._sortable.destroy();
31 | });
32 | return ko.bindingHandlers.template.init(element, valueAccessor);
33 | },
34 | update = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) {
35 |
36 | //There seems to be some problems with updating the options of a sortable
37 | //Tested to change eventhandlers and the group options without any luck
38 |
39 | return ko.bindingHandlers.template.update(element, valueAccessor, allBindings, viewModel, bindingContext);
40 | },
41 | eventHandlers = (function (handlers) {
42 |
43 | var moveOperations = [],
44 | tryMoveOperation = function (e, itemVM, parentVM, collection, parentBindings) {
45 | //A move operation is the combination of a add and remove event, this is to make sure that we have both the target and origin collections
46 | var currentOperation = { event: e, itemVM: itemVM, parentVM: parentVM, collection: collection, parentBindings: parentBindings },
47 | existingOperation = moveOperations.filter(function (op) {
48 | return op.itemVM === currentOperation.itemVM;
49 | })[0];
50 |
51 | if (!existingOperation) {
52 | moveOperations.push(currentOperation);
53 | }
54 | else {
55 | //We're finishing the operation and already have a handle on the operation item meaning that it's safe to remove it
56 | moveOperations.splice(moveOperations.indexOf(existingOperation), 1);
57 |
58 | var removeOperation = currentOperation.event.type === 'remove' ? currentOperation : existingOperation,
59 | addOperation = currentOperation.event.type === 'add' ? currentOperation : existingOperation;
60 |
61 | moveItem(itemVM, removeOperation.collection, addOperation.collection, addOperation.event.clone, addOperation.event);
62 | }
63 | },
64 | //Moves an item from the to (collection to from (collection), these can be references to the same collection which means it's a sort,
65 | //clone indicates if we should move or copy the item into the new collection
66 | moveItem = function (itemVM, from, to, clone, e) {
67 | //Unwrapping this allows us to manipulate the actual array
68 | var fromArray = from(),
69 | //It's not certain that the items actual index is the same as the index reported by sortable due to filtering etc.
70 | originalIndex = fromArray.indexOf(itemVM);
71 |
72 | //Remove sortables "unbound" element
73 | e.item.parentNode.removeChild(e.item);
74 |
75 | //This splice is necessary for both clone and move/sort
76 | //In sort/move since it shouldn't be at this index/in this array anymore
77 | //In clone since we have to work around knockouts valuHasMutated when manipulating arrays and avoid a "unbound" item added by sortable
78 | fromArray.splice(originalIndex, 1);
79 | //Update the array, this will also remove sortables "unbound" clone
80 | from.valueHasMutated();
81 | if (clone && from !== to) {
82 | //Readd the item
83 | fromArray.splice(originalIndex, 0, itemVM);
84 | //Force knockout to update
85 | from.valueHasMutated();
86 | }
87 | //Insert the item on its new position
88 | to().splice(e.newIndex, 0, itemVM);
89 | //Make sure to tell knockout that we've modified the actual array.
90 | to.valueHasMutated();
91 | };
92 |
93 | handlers.onRemove = tryMoveOperation;
94 | handlers.onAdd = tryMoveOperation;
95 | handlers.onUpdate = function (e, itemVM, parentVM, collection, parentBindings) {
96 | //This will be performed as a sort since the to/from collections reference the same collection and clone is set to false
97 | moveItem(itemVM, collection, collection, false, e);
98 | };
99 |
100 | return handlers;
101 | })({}),
102 | //bindingOptions are the options set in the "data-bind" attribute in the ui.
103 | //options are custom options, for instance draggable/sortable specific options
104 | buildOptions = function (bindingOptions, options) {
105 | //deep clone/copy of properties from the "from" argument onto the "into" argument and returns the modified "into"
106 | var merge = function (into, from) {
107 | for (var prop in from) {
108 | if (Object.prototype.toString.call(from[prop]) === '[object Object]') {
109 | if (Object.prototype.toString.call(into[prop]) !== '[object Object]') {
110 | into[prop] = {};
111 | }
112 | into[prop] = merge(into[prop], from[prop]);
113 | }
114 | else
115 | into[prop] = from[prop];
116 | }
117 |
118 | return into;
119 | },
120 | //unwrap the supplied options
121 | unwrappedOptions = ko.utils.peekObservable(bindingOptions()).options || {};
122 |
123 | //Make sure that we don't modify the provided settings object
124 | options = merge({}, options);
125 |
126 | //group is handled differently since we should both allow to change a draggable to a sortable (and vice versa),
127 | //but still be able to set a name on a draggable without it becoming a drop target.
128 | if (unwrappedOptions.group && Object.prototype.toString.call(unwrappedOptions.group) !== '[object Object]') {
129 | //group property is a name string declaration, convert to object.
130 | unwrappedOptions.group = { name: unwrappedOptions.group };
131 | }
132 |
133 | return merge(options, unwrappedOptions);
134 | };
135 |
136 | ko.bindingHandlers.draggable = {
137 | sortableOptions: {
138 | group: { pull: 'clone', put: false },
139 | sort: false
140 | },
141 | init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
142 | return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions);
143 | },
144 | update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
145 | return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions);
146 | }
147 | };
148 |
149 | ko.bindingHandlers.sortable = {
150 | sortableOptions: {
151 | group: { pull: true, put: true }
152 | },
153 | init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
154 | return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions);
155 | },
156 | update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
157 | return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions);
158 | }
159 | };
160 |
161 | })();
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/README.md:
--------------------------------------------------------------------------------
1 | Reactive reorderable lists with [Sortable](http://rubaxa.github.io/Sortable/),
2 | backed by [Meteor.js](http://meteor.com) collections:
3 |
4 | * new elements arriving in the collection will update the list as you expect
5 | * elements removed from the collection will be removed from the list
6 | * drag and drop between lists updates collections accordingly
7 |
8 | Demo: http://rubaxa-sortable.meteor.com
9 |
10 | # Meteor
11 |
12 | If you're new to Meteor, here's what the excitement is all about -
13 | [watch the first two minutes](https://www.youtube.com/watch?v=fsi0aJ9yr2o); you'll be hooked by 1:28.
14 | That screencast is from 2012. In the meantime, Meteor has become a mature JavaScript-everywhere web
15 | development framework. Read more at [Why Meteor](http://www.meteorpedia.com/read/Why_Meteor).
16 |
17 |
18 | # Usage
19 |
20 | Simplest invocation - order will be lost when the page is refreshed:
21 |
22 | ```handlebars
23 | {{sortable }}
24 | ```
25 |
26 | Persist the sort order in the 'order' field of each document in the collection:
27 |
28 | ```handlebars
29 | {{sortable items= sortField="order"}}
30 | ```
31 |
32 | Along with `items`, `sortField` is the only Meteor-specific option. If it's missing, the package will
33 | assume there is a field called "order" in the collection, holding unique `Number`s such that every
34 | `order` differs from that before and after it by at least 1. Basically, keep to 0, 1, 2, ... .
35 | Try not to depend on a particular format for this field; it *is* though guaranteed that a `sort` will
36 | produce lexicographical order, and that the order will be maintained after an arbitrary number of
37 | reorderings, unlike with [naive solutions](http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible).
38 |
39 |
40 | ## Passing options to the Sortable library
41 |
42 | {{sortable items= option1=value1 option2=value2...}}
43 | {{sortable items= options=myOptions}}
44 |
45 | For available options, please refer to [the main README](../README.md#options). You can pass them directly
46 | or under the `options` object. Direct options (`key=value`) override those in `options`. It is best
47 | to pass presentation-related options directly, and functionality-related settings in an `options`
48 | object, as this will enable designers to work without needing to inspect the JavaScript code:
49 |
50 |
51 | ...
52 | {{sortable items=Players handle=".sortable-handle" ghostClass="sortable-ghost" options=playerOptions}}
53 |
54 |
55 | Define the options in a helper for the template that calls Sortable:
56 |
57 | ```js
58 | Template.myTemplate.helpers({
59 | playerOptions: function () {
60 | return {
61 | group: {
62 | name: "league",
63 | pull: true,
64 | put: false
65 | },
66 | sort: false
67 | };
68 | }
69 | });
70 | ```
71 |
72 |
73 | ## Events
74 |
75 | All the original Sortable events are supported. In addition, they will receive
76 | the data context in `event.data`. You can access `event.data.order` this way:
77 |
78 | ```handlebars
79 | {{sortable items=players options=playersOptions}}
80 | ```
81 |
82 | ```js
83 | Template.myTemplate.helpers({
84 | playersOptions: function () {
85 | return {
86 | onSort: function(/**Event*/event) {
87 | console.log('Moved player #%d from %d to %d',
88 | event.data.order, event.oldIndex, event.newIndex
89 | );
90 | }
91 | };
92 | }
93 | });
94 | ```
95 |
96 |
97 | # Issues
98 |
99 | If you encounter an issue while using this package, please CC @dandv when you file it in this repo.
100 |
101 |
102 | # TODO
103 |
104 | * Array support
105 | * Tests
106 | * Misc. - see reactivize.js
107 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | meteor-platform
7 | autopublish
8 | insecure
9 | rubaxa:sortable
10 | dburles:mongo-collection-instances
11 | fezvrasta:bootstrap-material-design
12 | # twbs:bootstrap
13 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.0.1
2 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/.meteor/versions:
--------------------------------------------------------------------------------
1 | application-configuration@1.0.3
2 | autopublish@1.0.1
3 | autoupdate@1.1.3
4 | base64@1.0.1
5 | binary-heap@1.0.1
6 | blaze-tools@1.0.1
7 | blaze@2.0.3
8 | boilerplate-generator@1.0.1
9 | callback-hook@1.0.1
10 | check@1.0.2
11 | ctl-helper@1.0.4
12 | ctl@1.0.2
13 | dburles:mongo-collection-instances@0.2.5
14 | ddp@1.0.12
15 | deps@1.0.5
16 | ejson@1.0.4
17 | fastclick@1.0.1
18 | fezvrasta:bootstrap-material-design@0.2.1
19 | follower-livedata@1.0.2
20 | geojson-utils@1.0.1
21 | html-tools@1.0.2
22 | htmljs@1.0.2
23 | http@1.0.8
24 | id-map@1.0.1
25 | insecure@1.0.1
26 | jquery@1.0.1
27 | json@1.0.1
28 | launch-screen@1.0.0
29 | livedata@1.0.11
30 | logging@1.0.5
31 | meteor-platform@1.2.0
32 | meteor@1.1.3
33 | minifiers@1.1.2
34 | minimongo@1.0.5
35 | mobile-status-bar@1.0.1
36 | mongo@1.0.9
37 | observe-sequence@1.0.3
38 | ordered-dict@1.0.1
39 | random@1.0.1
40 | reactive-dict@1.0.4
41 | reactive-var@1.0.3
42 | reload@1.1.1
43 | retry@1.0.1
44 | routepolicy@1.0.2
45 | rubaxa:sortable@1.0.0
46 | session@1.0.4
47 | spacebars-compiler@1.0.3
48 | spacebars@1.0.3
49 | templating@1.0.9
50 | tracker@1.0.3
51 | twbs:bootstrap@3.3.1
52 | ui@1.0.4
53 | underscore@1.0.1
54 | url@1.0.2
55 | webapp-hashing@1.0.1
56 | webapp@1.1.4
57 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/README.md:
--------------------------------------------------------------------------------
1 | # RubaXa:Sortable Meteor demo
2 |
3 | This demo showcases the two-way integration between the reorderable list
4 | widget [Sortable](https://github.com/RubaXa/Sortable/) and Meteor.js. Meteor
5 | Mongo collections are updated when items are added, removed or reordered, and
6 | the order is persisted.
7 |
8 | It also shows list grouping and control over what lists can give or receive
9 | elements. You can only drag elements from the list to the left onto the list
10 | to the right.
11 |
12 | ## Usage
13 |
14 | The example uses the local package from the checkout, so it needs to wire
15 | up some files (`package.js` and `package.json`). This is done by the handy
16 | run script:
17 |
18 | ### Windows
19 |
20 | git clone https://github.com/RubaXa/Sortable.git
21 | cd Sortable
22 | git checkout dev
23 | cd meteor\example
24 | run.bat
25 |
26 | ### Elsewhere
27 |
28 | git clone https://github.com/RubaXa/Sortable.git
29 | cd Sortable
30 | git checkout dev
31 | meteor/example./run.sh
32 |
33 | ## Prior art
34 |
35 | ### Differential
36 |
37 | Differential wrote [a blog post on reorderable lists with
38 | Meteor](differential.com/blog/sortable-lists-in-meteor-using-jquery-ui) and
39 | [jQuery UI Sortable](http://jqueryui.com/sortable/). It served as inspiration
40 | for integrating [rubaxa:sortable](rubaxa.github.io/Sortable/),
41 | which uses the HTML5 native drag&drop API (not without [its
42 | limitations](https://github.com/RubaXa/Sortable/issues/106)).
43 | The reordering method used by the Differential example can lead to data loss
44 | though, because it calculates the new order of a dropped element as the
45 | arithmetic mean of the elements before and after it. This [runs into limitations
46 | of floating point precision](http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible)
47 | in JavaScript after <50 reorderings.
48 |
49 | ### Todos animated
50 |
51 | http://todos-dnd-animated.meteor.com/ ([source](https://github.com/nleush/meteor-todos-sortable-animation))
52 | is based on old Meteor Blaze (back then Spark) API, and won't work with current versions.
53 | It does showcase some neat features, such as animation when collection elements
54 | are reordered by another client. It uses jQuery UI Sortable as well, which lacks
55 | some features vs. rubaxa:Sortable, e.g. text selection within the item.
56 |
57 | ## TODO
58 |
59 | * Animation
60 | * Indication that an item is being edited
61 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/client/define-object-type.css:
--------------------------------------------------------------------------------
1 | .glyphicon {
2 | vertical-align: baseline;
3 | font-size: 80%;
4 | margin-right: 0.5em;
5 | }
6 |
7 | [class^="mdi-"], [class*=" mdi-"] {
8 | vertical-align: baseline;
9 | font-size: 90%;
10 | margin-right: 0.4em;
11 | }
12 |
13 | .list-pair {
14 | display: flex; /* use the flexbox model */
15 | flex-direction: row;
16 | }
17 | .sortable {
18 | /* font-size: 2em;*/
19 | }
20 |
21 | .sortable.source {
22 | /*background: #9FA8DA;*/
23 | flex: 0 0 auto;
24 | margin-right: 1em;
25 | cursor: move;
26 | cursor: -webkit-grabbing;
27 | }
28 |
29 | .sortable.target {
30 | /*background: #3F51B5;*/
31 | flex: 1 1 auto;
32 | margin-left: 1em;
33 | }
34 |
35 | .target .well {
36 |
37 | }
38 |
39 | .sortable-handle {
40 | cursor: move;
41 | cursor: -webkit-grabbing;
42 | }
43 | .sortable-handle.pull-right {
44 | margin-top: 0.3em;
45 | }
46 |
47 | .sortable-ghost {
48 | opacity: 0.6;
49 | }
50 |
51 | /* show the remove button on hover */
52 | .removable .close {
53 | display: none;
54 | }
55 | .removable:hover .close {
56 | display: block;
57 | }
58 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/client/define-object-type.html:
--------------------------------------------------------------------------------
1 |
2 | Reactive RubaXa:Sortable for Meteor
3 |
4 |
5 |
6 | {{> navbar}}
7 |
8 |
9 |
10 |
RubaXa:Sortable - reactive reorderable lists for Meteor
11 |
Drag attribute types from the left to define an object type on the right
94 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/client/define-object-type.js:
--------------------------------------------------------------------------------
1 | // Define an object type by dragging together attributes
2 |
3 | Template.typeDefinition.helpers({
4 | types: function () {
5 | return Types.find({}, { sort: { order: 1 } });
6 | },
7 | typesOptions: {
8 | sortField: 'order', // defaults to 'order' anyway
9 | group: {
10 | name: 'typeDefinition',
11 | pull: 'clone',
12 | put: false
13 | },
14 | sort: false // don't allow reordering the types, just the attributes below
15 | },
16 |
17 | attributes: function () {
18 | return Attributes.find({}, {
19 | sort: { order: 1 },
20 | transform: function (doc) {
21 | doc.icon = Types.findOne({name: doc.type}).icon;
22 | return doc;
23 | }
24 | });
25 | },
26 | attributesOptions: {
27 | group: {
28 | name: 'typeDefinition',
29 | put: true
30 | },
31 | onAdd: function (event) {
32 | delete event.data._id; // Generate a new id when inserting in the Attributes collection. Otherwise, if we add the same type twice, we'll get an error that the ids are not unique.
33 | delete event.data.icon;
34 | event.data.type = event.data.name;
35 | event.data.name = 'Rename me (double click)'
36 | },
37 | // event handler for reordering attributes
38 | onSort: function (event) {
39 | console.log('Item %s went from #%d to #%d',
40 | event.data.name, event.oldIndex, event.newIndex
41 | );
42 | }
43 | }
44 | });
45 |
46 | Template.sortableItemTarget.events({
47 | 'dblclick .name': function (event, template) {
48 | // Make the name editable. We should use an existing component, but it's
49 | // in a sorry state - https://github.com/arillo/meteor-x-editable/issues/1
50 | var name = template.$('.name');
51 | var input = template.$('input');
52 | if (input.length) { // jQuery never returns null - http://stackoverflow.com/questions/920236/how-can-i-detect-if-a-selector-returns-null
53 | input.show();
54 | } else {
55 | input = $('');
56 | name.after(input);
57 | }
58 | name.hide();
59 | input.focus();
60 | },
61 | 'blur input[type=text]': function (event, template) {
62 | // commit the change to the name, if any
63 | var input = template.$('input');
64 | input.hide();
65 | template.$('.name').show();
66 | // TODO - what is the collection here? We'll hard-code for now.
67 | // https://github.com/meteor/meteor/issues/3303
68 | if (this.name !== input.val() && this.name !== '')
69 | Attributes.update(this._id, {$set: {name: input.val()}});
70 | },
71 | 'keydown input[type=text]': function (event, template) {
72 | if (event.which === 27) {
73 | // ESC - discard edits and keep existing value
74 | template.$('input').val(this.name);
75 | event.preventDefault();
76 | event.target.blur();
77 | } else if (event.which === 13) {
78 | // ENTER
79 | event.preventDefault();
80 | event.target.blur();
81 | }
82 | }
83 | });
84 |
85 | // you can add events to all Sortable template instances
86 | Template.sortable.events({
87 | 'click .close': function (event, template) {
88 | // `this` is the data context set by the enclosing block helper (#each, here)
89 | template.collection.remove(this._id);
90 | // custom code, working on a specific collection
91 | if (Attributes.find().count() === 0) {
92 | Meteor.setTimeout(function () {
93 | Attributes.insert({
94 | name: 'Not nice to delete the entire list! Add some attributes instead.',
95 | type: 'String',
96 | order: 0
97 | })
98 | }, 1000);
99 | }
100 | }
101 | });
102 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/model.js:
--------------------------------------------------------------------------------
1 | Types = new Mongo.Collection('types');
2 | Attributes = new Mongo.Collection('attributes');
3 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/package.json:
--------------------------------------------------------------------------------
1 | ../../package.json
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/packages/Sortable:
--------------------------------------------------------------------------------
1 | ../../../
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/run.bat:
--------------------------------------------------------------------------------
1 | mklink ..\..\package.js "meteor/package.js"
2 | mklink package.json "../../package.json"
3 | meteor run
4 | del ..\..\package.js package.json
5 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/run.sh:
--------------------------------------------------------------------------------
1 | # sanity check: make sure we're in the root directory of the example
2 | cd "$( dirname "$0" )"
3 |
4 | # delete temp files even if Ctrl+C is pressed
5 | int_trap() {
6 | echo "Cleaning up..."
7 | }
8 | trap int_trap INT
9 |
10 | ln -s "meteor/package.js" ../../package.js 2>/dev/null
11 | ln -s "../../package.json" package.json 2>/dev/null
12 |
13 | meteor run "$@"
14 |
15 | rm ../../package.js package.json
16 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/example/server/fixtures.js:
--------------------------------------------------------------------------------
1 | Meteor.startup(function () {
2 | if (Types.find().count() === 0) {
3 | [
4 | {
5 | name: 'String',
6 | icon: ''
7 | },
8 | {
9 | name: 'Text, multi-line',
10 | icon: ''
11 | },
12 | {
13 | name: 'Category',
14 | icon: ''
15 | },
16 | {
17 | name: 'Number',
18 | icon: ''
19 | },
20 | {
21 | name: 'Date',
22 | icon: ''
23 | },
24 | {
25 | name: 'Hyperlink',
26 | icon: ''
27 | },
28 | {
29 | name: 'Image',
30 | icon: ''
31 | },
32 | {
33 | name: 'Progress',
34 | icon: ''
35 | },
36 | {
37 | name: 'Duration',
38 | icon: ''
39 | },
40 | {
41 | name: 'Map address',
42 | icon: ''
43 | },
44 | {
45 | name: 'Relationship',
46 | icon: ''
47 | }
48 | ].forEach(function (type, i) {
49 | Types.insert({
50 | name: type.name,
51 | icon: type.icon,
52 | order: i
53 | });
54 | }
55 | );
56 | console.log('Initialized attribute types.');
57 | }
58 |
59 | if (Attributes.find().count() === 0) {
60 | [
61 | { name: 'Name', type: 'String' },
62 | { name: 'Created at', type: 'Date' },
63 | { name: 'Link', type: 'Hyperlink' },
64 | { name: 'Owner', type: 'Relationship' }
65 | ].forEach(function (attribute, i) {
66 | Attributes.insert({
67 | name: attribute.name,
68 | type: attribute.type,
69 | order: i
70 | });
71 | }
72 | );
73 | console.log('Created sample object type.');
74 | }
75 | });
76 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/methods.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Meteor.methods({
4 | /**
5 | * Update the orderField of documents with given ids in a collection, incrementing it by incDec
6 | * @param {String} collectionName - name of the collection to update
7 | * @param {String[]} ids - array of document ids
8 | * @param {String} orderField - the name of the order field, usually "order"
9 | * @param {Number} incDec - pass 1 or -1
10 | */
11 | 'rubaxa:sortable/collection-update': function (collectionName, ids, orderField, incDec) {
12 | check(collectionName, String);
13 | check(ids, [String]);
14 | check(orderField, String);
15 | check(incDec, Number);
16 | var selector = {_id: {$in: ids}}, modifier = {$inc: {}};
17 | modifier.$inc[orderField] = incDec;
18 | Mongo.Collection.get(collectionName).update(selector, modifier, {multi: true});
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/package.js:
--------------------------------------------------------------------------------
1 | // package metadata file for Meteor.js
2 | 'use strict';
3 |
4 | var packageName = 'rubaxa:sortable'; // http://atmospherejs.com/rubaxa/sortable
5 |
6 | var packageJson = JSON.parse(Npm.require("fs").readFileSync('package.json'));
7 |
8 | Package.describe({
9 | name: packageName,
10 | summary: 'Sortable: reactive minimalist reorderable drag-and-drop lists on modern browsers and touch devices',
11 | version: packageJson.version,
12 | git: 'https://github.com/RubaXa/Sortable.git',
13 | readme: 'https://github.com/RubaXa/Sortable/blob/master/meteor/README.md'
14 | });
15 |
16 | Package.onUse(function (api) {
17 | api.versionsFrom(['METEOR@0.9.0', 'METEOR@1.0']);
18 | api.use('templating', 'client');
19 | api.use('dburles:mongo-collection-instances@0.2.6'); // to watch collections getting created
20 | api.export('Sortable');
21 | api.addFiles([
22 | 'Sortable.js',
23 | 'meteor/template.html', // the HTML comes first, so reactivize.js can refer to the template in it
24 | 'meteor/reactivize.js'
25 | ], 'client');
26 | api.addFiles('meteor/methods.js'); // add to both client and server
27 | });
28 |
29 | Package.onTest(function (api) {
30 | api.use(packageName, 'client');
31 | api.use('tinytest', 'client');
32 |
33 | api.addFiles('meteor/test.js', 'client');
34 | });
35 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Publish package to Meteor's repository, Atmospherejs.com
3 |
4 | # Make sure Meteor is installed, per https://www.meteor.com/install.
5 | # The curl'ed script is totally safe; takes 2 minutes to read its source and check.
6 | type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; }
7 |
8 | # sanity check: make sure we're in the root directory of the checkout
9 | cd "$( dirname "$0" )/.."
10 |
11 | ALL_EXIT_CODE=0
12 |
13 | # test any package*.js packages we may have, e.g. package.js, package-compat.js
14 | for PACKAGE_FILE in meteor/package*.js; do
15 |
16 | # Meteor expects package.js to be in the root directory of the checkout, so copy there our package file under that name, temporarily
17 | cp $PACKAGE_FILE ./package.js
18 |
19 | # publish package, creating it if it's the first time we're publishing
20 | PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2)
21 |
22 | echo "Publishing $PACKAGE_NAME..."
23 |
24 | # Attempt to re-publish the package - the most common operation once the initial release has
25 | # been made. If the package name was changed (rare), you'll have to pass the --create flag.
26 | meteor publish "$@"; EXIT_CODE=$?
27 | ALL_EXIT_CODE=$(( $ALL_EXIT_CODE + $EXIT_CODE ))
28 | if (( $EXIT_CODE == 0 )); then
29 | echo "Thanks for releasing a new version. You can see it at"
30 | echo "https://atmospherejs.com/${PACKAGE_NAME/://}"
31 | else
32 | echo "We got an error. Please post it at https://github.com/raix/Meteor-community-discussions/issues/14"
33 | fi
34 |
35 | # rm the temporary build files and package.js
36 | rm -rf ".build.$PACKAGE_NAME" versions.json package.js
37 |
38 | done
39 |
40 | exit $ALL_EXIT_CODE
41 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/reactivize.js:
--------------------------------------------------------------------------------
1 | /*
2 | Make a Sortable reactive by binding it to a Mongo.Collection.
3 | Calls `rubaxa:sortable/collection-update` on the server to update the sortField or affected records.
4 |
5 | TODO:
6 | * supply consecutive values if the `order` field doesn't have any
7 | * .get(DOMElement) - return the Sortable object of a DOMElement
8 | * create a new _id automatically onAdd if the event.from list had pull: 'clone'
9 | * support arrays
10 | * sparse arrays
11 | * tests
12 | * drop onto existing empty lists
13 | * insert back into lists emptied by dropping
14 | * performance on dragging into long list at the beginning
15 | * handle failures on Collection operations, e.g. add callback to .insert
16 | * when adding elements, update ranks just for the half closer to the start/end of the list
17 | * revisit http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible
18 | * reproduce the insidious bug where the list isn't always sorted (fiddle with dragging #1 over #2, then back, then #N before #1)
19 |
20 | */
21 |
22 | 'use strict';
23 |
24 | Template.sortable.created = function () {
25 | var templateInstance = this;
26 | // `this` is a template instance that can store properties of our choice - http://docs.meteor.com/#/full/template_inst
27 | if (templateInstance.setupDone) return; // paranoid: only run setup once
28 | // this.data is the data context - http://docs.meteor.com/#/full/template_data
29 | // normalize all options into templateInstance.options, and remove them from .data
30 | templateInstance.options = templateInstance.data.options || {};
31 | Object.keys(templateInstance.data).forEach(function (key) {
32 | if (key === 'options' || key === 'items') return;
33 | templateInstance.options[key] = templateInstance.data[key];
34 | delete templateInstance.data[key];
35 | });
36 | templateInstance.options.sortField = templateInstance.options.sortField || 'order';
37 | // We can get the collection via the .collection property of the cursor, but changes made that way
38 | // will NOT be sent to the server - https://github.com/meteor/meteor/issues/3271#issuecomment-66656257
39 | // Thus we need to use dburles:mongo-collection-instances to get a *real* collection
40 | if (templateInstance.data.items && templateInstance.data.items.collection) {
41 | // cursor passed via items=; its .collection works client-only and has a .name property
42 | templateInstance.collectionName = templateInstance.data.items.collection.name;
43 | templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName);
44 | } else if (templateInstance.data.items) {
45 | // collection passed via items=; does NOT have a .name property, but _name
46 | templateInstance.collection = templateInstance.data.items;
47 | templateInstance.collectionName = templateInstance.collection._name;
48 | } else if (templateInstance.data.collection) {
49 | // cursor passed directly
50 | templateInstance.collectionName = templateInstance.data.collection.name;
51 | templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName);
52 | } else {
53 | templateInstance.collection = templateInstance.data; // collection passed directly
54 | templateInstance.collectionName = templateInstance.collection._name;
55 | }
56 |
57 | // TODO if (Array.isArray(templateInstance.collection))
58 |
59 | // What if user filters some of the items in the cursor, instead of ordering the entire collection?
60 | // Use case: reorder by preference movies of a given genre, a filter within all movies.
61 | // A: Modify all intervening items **that are on the client**, to preserve the overall order
62 | // TODO: update *all* orders via a server method that takes not ids, but start & end elements - mild security risk
63 | delete templateInstance.data.options;
64 |
65 | /**
66 | * When an element was moved, adjust its orders and possibly the order of
67 | * other elements, so as to maintain a consistent and correct order.
68 | *
69 | * There are three approaches to this:
70 | * 1) Using arbitrary precision arithmetic and setting only the order of the moved
71 | * element to the average of the orders of the elements around it -
72 | * http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible
73 | * The downside is that the order field in the DB will increase by one byte every
74 | * time an element is reordered.
75 | * 2) Adjust the orders of the intervening items. This keeps the orders sane (integers)
76 | * but is slower because we have to modify multiple documents.
77 | * TODO: we may be able to update fewer records by only altering the
78 | * order of the records between the newIndex/oldIndex and the start/end of the list.
79 | * 3) Use regular precision arithmetic, but when the difference between the orders of the
80 | * moved item and the one before/after it falls below a certain threshold, adjust
81 | * the order of that other item, and cascade doing so up or down the list.
82 | * This will keep the `order` field constant in size, and will only occasionally
83 | * require updating the `order` of other records.
84 | *
85 | * For now, we use approach #2.
86 | *
87 | * @param {String} itemId - the _id of the item that was moved
88 | * @param {Number} orderPrevItem - the order of the item before it, or null
89 | * @param {Number} orderNextItem - the order of the item after it, or null
90 | */
91 | templateInstance.adjustOrders = function adjustOrders(itemId, orderPrevItem, orderNextItem) {
92 | var orderField = templateInstance.options.sortField;
93 | var selector = {}, modifier = {$set: {}};
94 | var ids = [];
95 | var startOrder = templateInstance.collection.findOne(itemId)[orderField];
96 | if (orderPrevItem !== null) {
97 | // Element has a previous sibling, therefore it was moved down in the list.
98 | // Decrease the order of intervening elements.
99 | selector[orderField] = {$lte: orderPrevItem, $gt: startOrder};
100 | ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id');
101 | Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, -1);
102 |
103 | // Set the order of the dropped element to the order of its predecessor, whose order was decreased
104 | modifier.$set[orderField] = orderPrevItem;
105 | } else {
106 | // element moved up the list, increase order of intervening elements
107 | selector[orderField] = {$gte: orderNextItem, $lt: startOrder};
108 | ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id');
109 | Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, 1);
110 |
111 | // Set the order of the dropped element to the order of its successor, whose order was increased
112 | modifier.$set[orderField] = orderNextItem;
113 | }
114 | templateInstance.collection.update(itemId, modifier);
115 | };
116 |
117 | templateInstance.setupDone = true;
118 | };
119 |
120 |
121 | Template.sortable.rendered = function () {
122 | var templateInstance = this;
123 | var orderField = templateInstance.options.sortField;
124 |
125 | // sorting was changed within the list
126 | var optionsOnUpdate = templateInstance.options.onUpdate;
127 | templateInstance.options.onUpdate = function sortableUpdate(/**Event*/event) {
128 | var itemEl = event.item; // dragged HTMLElement
129 | event.data = Blaze.getData(itemEl);
130 | if (event.newIndex < event.oldIndex) {
131 | // Element moved up in the list. The dropped element has a next sibling for sure.
132 | var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField];
133 | templateInstance.adjustOrders(event.data._id, null, orderNextItem);
134 | } else if (event.newIndex > event.oldIndex) {
135 | // Element moved down in the list. The dropped element has a previous sibling for sure.
136 | var orderPrevItem = Blaze.getData(itemEl.previousElementSibling)[orderField];
137 | templateInstance.adjustOrders(event.data._id, orderPrevItem, null);
138 | } else {
139 | // do nothing - drag and drop in the same location
140 | }
141 | if (optionsOnUpdate) optionsOnUpdate(event);
142 | };
143 |
144 | // element was added from another list
145 | var optionsOnAdd = templateInstance.options.onAdd;
146 | templateInstance.options.onAdd = function sortableAdd(/**Event*/event) {
147 | var itemEl = event.item; // dragged HTMLElement
148 | event.data = Blaze.getData(itemEl);
149 | // let the user decorate the object with additional properties before insertion
150 | if (optionsOnAdd) optionsOnAdd(event);
151 |
152 | // Insert the new element at the end of the list and move it where it was dropped.
153 | // We could insert it at the beginning, but that would lead to negative orders.
154 | var sortSpecifier = {}; sortSpecifier[orderField] = -1;
155 | event.data.order = templateInstance.collection.findOne({}, { sort: sortSpecifier, limit: 1 }).order + 1;
156 | // TODO: this can obviously be optimized by setting the order directly as the arithmetic average, with the caveats described above
157 | var newElementId = templateInstance.collection.insert(event.data);
158 | event.data._id = newElementId;
159 | if (itemEl.nextElementSibling) {
160 | var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField];
161 | templateInstance.adjustOrders(newElementId, null, orderNextItem);
162 | } else {
163 | // do nothing - inserted after the last element
164 | }
165 | // remove the dropped HTMLElement from the list because we have inserted it in the collection, which will update the template
166 | itemEl.parentElement.removeChild(itemEl);
167 | };
168 |
169 | // element was removed by dragging into another list
170 | var optionsOnRemove = templateInstance.options.onRemove;
171 | templateInstance.options.onRemove = function sortableRemove(/**Event*/event) {
172 | var itemEl = event.item; // dragged HTMLElement
173 | event.data = Blaze.getData(itemEl);
174 | // don't remove from the collection if group.pull is clone or false
175 | if (typeof templateInstance.options.group === 'undefined'
176 | || typeof templateInstance.options.group.pull === 'undefined'
177 | || templateInstance.options.group.pull === true
178 | ) templateInstance.collection.remove(event.data._id);
179 | if (optionsOnRemove) optionsOnRemove(event);
180 | };
181 |
182 | // just compute the `data` context
183 | ['onStart', 'onEnd', 'onSort', 'onFilter'].forEach(function (eventHandler) {
184 | if (templateInstance.options[eventHandler]) {
185 | var userEventHandler = templateInstance.options[eventHandler];
186 | templateInstance.options[eventHandler] = function (/**Event*/event) {
187 | var itemEl = event.item; // dragged HTMLElement
188 | event.data = Blaze.getData(itemEl);
189 | userEventHandler(event);
190 | };
191 | }
192 | });
193 |
194 | templateInstance.sortable = Sortable.create(templateInstance.firstNode.parentElement, templateInstance.options);
195 | // TODO make the object accessible, e.g. via Sortable.getSortableById() or some such
196 | };
197 |
198 |
199 | Template.sortable.destroyed = function () {
200 | this.sortable.destroy();
201 | };
202 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/runtests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Test Meteor package before publishing to Atmospherejs.com
3 |
4 | # Make sure Meteor is installed, per https://www.meteor.com/install.
5 | # The curl'ed script is totally safe; takes 2 minutes to read its source and check.
6 | type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; }
7 |
8 | # sanity check: make sure we're in the root directory of the checkout
9 | cd "$( dirname "$0" )/.."
10 |
11 |
12 | # delete the temporary files even if Ctrl+C is pressed
13 | int_trap() {
14 | printf "\nTests interrupted. Cleaning up...\n\n"
15 | }
16 | trap int_trap INT
17 |
18 |
19 | ALL_EXIT_CODE=0
20 |
21 | # test any package*.js packages we may have, e.g. package.js, package-standalone.js
22 | for PACKAGE_FILE in meteor/package*.js; do
23 |
24 | # Meteor expects package.js in the root dir of the checkout, so copy there our package file under that name, temporarily
25 | cp $PACKAGE_FILE ./package.js
26 |
27 | PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2)
28 |
29 | echo "### Testing $PACKAGE_NAME..."
30 |
31 | # provide an invalid MONGO_URL so Meteor doesn't bog us down with an empty Mongo database
32 | if [ $# -gt 0 ]; then
33 | # interpret any parameter to mean we want an interactive test
34 | MONGO_URL=mongodb:// meteor test-packages ./
35 | else
36 | # automated/CI test with phantomjs
37 | ./node_modules/.bin/spacejam --mongo-url mongodb:// test-packages ./
38 | ALL_EXIT_CODES=$(( $ALL_EXIT_CODES + $? ))
39 | fi
40 |
41 | # delete temporary build files and package.js
42 | rm -rf .build.* versions.json package.js
43 |
44 | done
45 |
46 | exit $ALL_EXIT_CODES
47 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/template.html:
--------------------------------------------------------------------------------
1 |
2 | {{#each items}}
3 | {{> Template.contentBlock this}}
4 | {{/each}}
5 |
6 |
--------------------------------------------------------------------------------
/assets/js/sortable/meteor/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Tinytest.add('Sortable.is', function (test) {
4 | var items = document.createElement('ul');
5 | items.innerHTML = '