jquery.galleryManager.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. (function ($) {
  2. 'use strict';
  3. var galleryDefaults = {
  4. csrfToken: $('meta[name=csrf-token]').attr('content'),
  5. csrfTokenName: $('meta[name=csrf-param]').attr('content'),
  6. nameLabel: 'Name',
  7. descriptionLabel: 'Description',
  8. hasName: true,
  9. hasDesc: true,
  10. uploadUrl: '',
  11. deleteUrl: '',
  12. updateUrl: '',
  13. arrangeUrl: '',
  14. photos: []
  15. };
  16. function galleryManager(el, options) {
  17. //Extending options:
  18. var opts = $.extend({}, galleryDefaults, options);
  19. //code
  20. var csrfParams = opts.csrfToken ? '&' + opts.csrfTokenName + '=' + opts.csrfToken : '';
  21. var photos = {}; // photo elements by id
  22. var $gallery = $(el);
  23. if (!opts.hasName) {
  24. if (!opts.hasDesc) {
  25. $gallery.addClass('no-name-no-desc');
  26. $('.edit_selected', $gallery).hide();
  27. }
  28. else $gallery.addClass('no-name');
  29. } else if (!opts.hasDesc)
  30. $gallery.addClass('no-desc');
  31. var $sorter = $('.sorter', $gallery);
  32. var $images = $('.images', $sorter);
  33. var $editorModal = $('.editor-modal', $gallery);
  34. var $progressOverlay = $('.progress-overlay', $gallery);
  35. var $uploadProgress = $('.upload-progress', $progressOverlay);
  36. var $editorForm = $('.form', $editorModal);
  37. function htmlEscape(str) {
  38. return String(str)
  39. .replace(/&/g, '&')
  40. .replace(/"/g, '"')
  41. .replace(/'/g, ''')
  42. .replace(/</g, '&lt;')
  43. .replace(/>/g, '&gt;');
  44. }
  45. function createEditorElement(id, src, name, description) {
  46. var html = '<div class="photo-editor row">' +
  47. '<div class="col-xs-4">' +
  48. '<img src="' + htmlEscape(src) + '" style="max-width:100%;">' +
  49. '</div>' +
  50. '<div class="col-xs-8">' +
  51. (opts.hasName
  52. ?
  53. '<div class="form-group">' +
  54. '<label class="control-label" for="photo_name_' + id + '">' + opts.nameLabel + ':</label>' +
  55. '<input class="form-control" type="text" name="photo[' + id + '][name]" class="input-xlarge" value="' + htmlEscape(name) + '" id="photo_name_' + id + '"/>' +
  56. '</div>' : '') +
  57. (opts.hasDesc
  58. ?
  59. '<div class="form-group">' +
  60. '<label class="control-label" for="photo_description_' + id + '">' + opts.descriptionLabel + ':</label>' +
  61. '<textarea class="form-control" name="photo[' + id + '][description]" rows="3" cols="40" class="input-xlarge" id="photo_description_' + id + '">' + htmlEscape(description) + '</textarea>' +
  62. '</div>' : '') +
  63. '</div>' +
  64. '</div>';
  65. return $(html);
  66. }
  67. var photoTemplate = '<div class="photo">' + '<div class="image-preview"><img src=""/></div><div class="caption">';
  68. if (opts.hasName) {
  69. photoTemplate += '<h5></h5>';
  70. }
  71. if (opts.hasDesc) {
  72. photoTemplate += '<p></p>';
  73. }
  74. photoTemplate += '</div><div class="actions">';
  75. if (opts.hasName || opts.hasDesc) {
  76. photoTemplate += '<span class="editPhoto btn btn-primary btn-xs"><i class="fas fa-edit"></i></span> ';
  77. }
  78. photoTemplate += '<span class="deletePhoto btn btn-danger btn-xs"><i class="fas fa-trash"></i></span>' +
  79. '</div><input type="checkbox" class="photo-select"/></div>';
  80. function addPhoto(id, src, name, description, rank) {
  81. var photo = $(photoTemplate);
  82. photos[id] = photo;
  83. photo.data('id', id);
  84. photo.data('rank', rank);
  85. $('img', photo).attr('src', src);
  86. if (opts.hasName){
  87. $('.caption h5', photo).text(name);
  88. }
  89. if (opts.hasDesc){
  90. $('.caption p', photo).text(description);
  91. }
  92. $images.append(photo);
  93. return photo;
  94. }
  95. function editPhotos(ids) {
  96. var l = ids.length;
  97. var form = $editorForm.empty();
  98. for (var i = 0; i < l; i++) {
  99. var id = ids[i];
  100. var photo = photos[id],
  101. src = $('img', photo).attr('src'),
  102. name = $('.caption h5', photo).text(),
  103. description = $('.caption p', photo).text();
  104. form.append(createEditorElement(id, src, name, description));
  105. }
  106. if (l > 0){
  107. $editorModal.modal('show');
  108. }
  109. }
  110. function removePhotos(ids) {
  111. $.ajax({
  112. type: 'POST',
  113. url: opts.deleteUrl,
  114. data: 'id[]=' + ids.join('&id[]=') + csrfParams,
  115. success: function (t) {
  116. if (t == 'OK') {
  117. for (var i = 0, l = ids.length; i < l; i++) {
  118. photos[ids[i]].remove();
  119. delete photos[ids[i]];
  120. }
  121. } else {
  122. alert(t);
  123. }
  124. }
  125. });
  126. }
  127. function deleteClick(e) {
  128. e.preventDefault();
  129. var photo = $(this).closest('.photo');
  130. var id = photo.data('id');
  131. // here can be question to confirm delete
  132. // if (!confirm(deleteConfirmation)) return false;
  133. removePhotos([id]);
  134. return false;
  135. }
  136. function editClick(e) {
  137. e.preventDefault();
  138. var photo = $(this).closest('.photo');
  139. var id = photo.data('id');
  140. editPhotos([id]);
  141. return false;
  142. }
  143. function updateButtons() {
  144. var selectedCount = $('.photo.selected', $sorter).length;
  145. $('.select_all', $gallery).prop('checked', $('.photo', $sorter).length == selectedCount);
  146. if (selectedCount == 0) {
  147. $('.edit_selected, .remove_selected', $gallery).addClass('disabled');
  148. } else {
  149. $('.edit_selected, .remove_selected', $gallery).removeClass('disabled');
  150. }
  151. }
  152. function selectChanged() {
  153. var $this = $(this);
  154. if ($this.is(':checked'))
  155. $this.closest('.photo').addClass('selected');
  156. else
  157. $this.closest('.photo').removeClass('selected');
  158. updateButtons();
  159. }
  160. $images
  161. .on('click', '.photo .deletePhoto', deleteClick)
  162. .on('click', '.photo .editPhoto', editClick)
  163. .on('click', '.photo .photo-select', selectChanged);
  164. $('.images', $sorter).sortable({tolerance: "pointer"}).disableSelection().bind("sortstop", function () {
  165. var data = [];
  166. $('.photo', $sorter).each(function () {
  167. var t = $(this);
  168. data.push('order[' + t.data('id') + ']=' + t.data('rank'));
  169. });
  170. $.ajax({
  171. type: 'POST',
  172. url: opts.arrangeUrl,
  173. data: data.join('&') + csrfParams,
  174. dataType: "json"
  175. }).done(function (data) {
  176. for (var id in data[id]) {
  177. photos[id].data('rank', data[id]);
  178. }
  179. // order saved!
  180. // we can inform user that order saved
  181. });
  182. });
  183. if (window.FormData !== undefined) { // if XHR2 available
  184. var uploadFileName = $('.afile', $gallery).attr('name');
  185. var multiUpload = function (files) {
  186. if (files.length == 0) return;
  187. $progressOverlay.show();
  188. $uploadProgress.css('width', '5%');
  189. var filesCount = files.length;
  190. var uploadedCount = 0;
  191. var ids = [];
  192. for (var i = 0; i < filesCount; i++) {
  193. var fd = new FormData();
  194. fd.append(uploadFileName, files[i]);
  195. if (opts.csrfToken) {
  196. fd.append(opts.csrfTokenName, opts.csrfToken);
  197. }
  198. var xhr = new XMLHttpRequest();
  199. xhr.open('POST', opts.uploadUrl, true);
  200. console.log(files[i]);
  201. xhr.onload = function () {
  202. uploadedCount++;
  203. if (this.status == 200) {
  204. var resp = JSON.parse(this.response);
  205. addPhoto(resp['id'], resp['preview'], resp['name'], resp['description'], resp['rank']);
  206. ids.push(resp['id']);
  207. } else {
  208. // exception !!!
  209. }
  210. $uploadProgress.css('width', '' + (5 + 95 * uploadedCount / filesCount) + '%');
  211. if (uploadedCount === filesCount) {
  212. $uploadProgress.css('width', '100%');
  213. $progressOverlay.hide();
  214. if (opts.hasName || opts.hasDesc) editPhotos(ids);
  215. }
  216. };
  217. xhr.upload.addEventListener("progress", (event) => {
  218. console.log(round(event.loaded *100/ event.total));
  219. });
  220. xhr.send(fd);
  221. }
  222. };
  223. (function () { // add drag and drop
  224. var el = $gallery[0];
  225. var isOver = false;
  226. var lastIsOver = false;
  227. setInterval(function () {
  228. if (isOver != lastIsOver) {
  229. if (isOver) el.classList.add('over');
  230. else el.classList.remove('over');
  231. lastIsOver = isOver
  232. }
  233. }, 30);
  234. function handleDragOver(e) {
  235. e.preventDefault();
  236. isOver = true;
  237. return false;
  238. }
  239. function handleDragLeave() {
  240. isOver = false;
  241. return false;
  242. }
  243. function handleDrop(e) {
  244. e.preventDefault();
  245. e.stopPropagation();
  246. var files = e.dataTransfer.files;
  247. multiUpload(files);
  248. isOver = false;
  249. return false;
  250. }
  251. function handleDragEnd() {
  252. isOver = false;
  253. }
  254. el.addEventListener('dragover', handleDragOver, false);
  255. el.addEventListener('dragleave', handleDragLeave, false);
  256. el.addEventListener('drop', handleDrop, false);
  257. el.addEventListener('dragend', handleDragEnd, false);
  258. })();
  259. $('.afile', $gallery).attr('multiple', 'true').on('change', function (e) {
  260. e.preventDefault();
  261. multiUpload(this.files);
  262. $(this).val(null);
  263. });
  264. } else {
  265. $('.afile', $gallery).on('change', function (e) {
  266. e.preventDefault();
  267. var ids = [];
  268. $progressOverlay.show();
  269. $uploadProgress.css('width', '5%');
  270. var data = {};
  271. if (opts.csrfToken)
  272. data[opts.csrfTokenName] = opts.csrfToken;
  273. $.ajax({
  274. type: 'POST',
  275. url: opts.uploadUrl,
  276. data: data,
  277. files: $(this),
  278. iframe: true,
  279. processData: false,
  280. dataType: "json"
  281. }).done(function (resp) {
  282. addPhoto(resp['id'], resp['preview'], resp['name'], resp['description'], resp['rank']);
  283. ids.push(resp['id']);
  284. $uploadProgress.css('width', '100%');
  285. $progressOverlay.hide();
  286. if (opts.hasName || opts.hasDesc) editPhotos(ids);
  287. });
  288. });
  289. }
  290. $('.save-changes', $editorModal).click(function (e) {
  291. e.preventDefault();
  292. $.post(opts.updateUrl, $('input, textarea', $editorForm).serialize() + csrfParams, function (data) {
  293. var count = data.length;
  294. for (var key = 0; key < count; key++) {
  295. var p = data[key];
  296. var photo = photos[p.id];
  297. $('img', photo).attr('src', p['src']);
  298. if (opts.hasName)
  299. $('.caption h5', photo).text(p['name']);
  300. if (opts.hasDesc)
  301. $('.caption p', photo).text(p['description']);
  302. }
  303. $editorModal.modal('hide');
  304. //deselect all items after editing
  305. $('.photo.selected', $sorter).each(function () {
  306. $('.photo-select', this).prop('checked', false)
  307. }).removeClass('selected');
  308. $('.select_all', $gallery).prop('checked', false);
  309. updateButtons();
  310. }, 'json');
  311. });
  312. $('.edit_selected', $gallery).click(function (e) {
  313. e.preventDefault();
  314. var ids = [];
  315. $('.photo.selected', $sorter).each(function () {
  316. ids.push($(this).data('id'));
  317. });
  318. editPhotos(ids);
  319. return false;
  320. });
  321. $('.remove_selected', $gallery).click(function (e) {
  322. e.preventDefault();
  323. var ids = [];
  324. $('.photo.selected', $sorter).each(function () {
  325. ids.push($(this).data('id'));
  326. });
  327. removePhotos(ids);
  328. });
  329. $('.select_all', $gallery).change(function () {
  330. if ($(this).prop('checked')) {
  331. $('.photo', $sorter).each(function () {
  332. $('.photo-select', this).prop('checked', true)
  333. }).addClass('selected');
  334. } else {
  335. $('.photo.selected', $sorter).each(function () {
  336. $('.photo-select', this).prop('checked', false)
  337. }).removeClass('selected');
  338. }
  339. updateButtons();
  340. });
  341. for (var i = 0, l = opts.photos.length; i < l; i++) {
  342. var resp = opts.photos[i];
  343. addPhoto(resp['id'], resp['preview'], resp['name'], resp['description'], resp['rank']);
  344. }
  345. }
  346. // The actual plugin
  347. $.fn.galleryManager = function (options) {
  348. if (this.length) {
  349. this.each(function () {
  350. galleryManager(this, options);
  351. });
  352. }
  353. };
  354. })(jQuery);