/** * @name LobiList - Responsive jQuery todo list plugin * LobiList is todo list jquery plugin. Support multiple list with different styles, communication to backend, drag & drop of todos * * @author Zura Sekhniashvili * @version 1.0.0 * @licence MIT */ $(function() { var LIST_COUNTER = 0; /** * List class * * @class * @param {Object} $lobiList - jQuery element * @param {Object} options - Options for List 'class' * @constructor */ var List = function($lobiList, options) { this.$lobiList = $lobiList; this.$options = options; this.$globalOptions = $lobiList.$options; this.$items = {}; this._init(); }; List.prototype = { $lobiList: null, $el: null, $elWrapper: null, $options: {}, $items: {}, $globalOptions: {}, $ul: null, $header: null, $title: null, $form: null, $footer: null, $body: null, eventsSuppressed: false, /** * * @private */ _init: function() { var me = this; me.suppressEvents(); if (!me.$options.id) { me.$options.id = 'lobilist-list-' + (LIST_COUNTER++); } var $wrapper = $('
'); var $div = $('
').appendTo($wrapper); if (me.$options.defaultStyle) { $div.addClass(me.$options.defaultStyle); } me.$el = $div; me.$elWrapper = $wrapper; me.$header = me._createHeader(); me.$title = me._createTitle(); me.$body = me._createBody(); me.$ul = me._createList(); if (me.$options.items) { me._createItems(me.$options.items); } me.$form = me._createForm(); me.$body.append(me.$ul, me.$form); me.$footer = me._createFooter(); if (me.$globalOptions.sortable) { me._enableSorting(); } me.resumeEvents(); }, /** * Add item. If action.insert url is provided request is sent to the server. * Server response example: {"success": Boolean}. * If response.success is true item is added. * Otherwise errorCallback callback is called if it was provided. * * @method addItem * @param {Object} item - The item Object * @param {Function} errorCallback - The callback which is called when server returned response but * response.success=false * @returns {List} */ addItem: function(item, errorCallback) { var me = this; if (me._triggerEvent('beforeItemAdd', [me, item]) === false) { return me; } item = me._processItemData(item); if (me.$globalOptions.actions.insert) { $.ajax(me.$globalOptions.actions.insert, { data: item, method: 'POST' }) //res is JSON object of format {"success": Boolean, "id": Number, "msg": String} .done(function(res) { if (res.success) { item.id = res.id; me._addItemToList(item); } else { if (errorCallback && typeof errorCallback === 'function') { errorCallback(res) } } }); } else { item.id = me.$lobiList.getNextId(); me._addItemToList(item); } return me; }, /** * Update item. If action.update url is provided request is sent to the server. * Server response example: {"success": Boolean}. * If response.success is true item is updated. * Otherwise errorCallback callback is called if it was provided. * * @method updateItem * @param {Object} item - The item Object to update * @param {Function} errorCallback - The callback which is called when server returned response but * response.success=false * @returns {List} */ updateItem: function(item, errorCallback) { var me = this; if (me._triggerEvent('beforeItemUpdate', [me, item]) === false) { return me } if (me.$globalOptions.actions.update) { $.ajax(me.$globalOptions.actions.update, { data: item, method: 'POST' }) //res is JSON object of format {"id": Number, "success": Boolean, "msg": String} .done(function(res) { if (res.success) { me._updateItemInList(item); } else { if (errorCallback && typeof errorCallback === 'function') { errorCallback(res) } } }); } else { me._updateItemInList(item); } return me; }, /** * Delete item from the list. If action.delete url is provided request is sent to the server. * Server response example: {"success": Boolean} * If response.success=true item is deleted from the list and afterItemDelete event * if triggered. Otherwise errorCallback callback is called if it was provided. * * @method deleteItem * @param {Object} item - The item Object to delete * @param {Function} errorCallback - The callback which is called when server returned response but * response.success=false * @returns {List} */ deleteItem: function(item, errorCallback) { var me = this; if (me._triggerEvent('beforeItemDelete', [me, item]) === false) { return me } if (me.$globalOptions.actions.delete) { return me._sendAjax(me.$globalOptions.actions.delete, { data: item, method: 'POST' }) //res is JSON object of format .done(function(res) { if (res.success) { me._removeItemFromList(item); } else { if (errorCallback && typeof errorCallback === 'function') { errorCallback(res) } } }); } else { me._removeItemFromList(item); } return me; }, /** * If item does not have id, it is considered as new and is added to the list. * If it has id it is updated. If update and insert actions are provided corresponding request is sent to the server * * @method saveOrUpdateItem * @param {Object} item - The item Object * @param {Function} errorCallback - The callback which is called when server returned response but * response.success=false * @returns {List} */ saveOrUpdateItem: function(item, errorCallback) { var me = this; if (item.id) { me.updateItem(item, errorCallback); } else { me.addItem(item, errorCallback); } return me; }, /** * Start title editing * * @method startTitleEditing * @returns {List} */ startTitleEditing: function() { var me = this; var input = me._createInput(); me.$title.attr('data-old-title', me.$title.html()); input.val(me.$title.html()); input.insertAfter(me.$title); me.$title.addClass('hide'); me.$header.addClass('title-editing'); input[0].focus(); input[0].select(); return me; }, /** * Finish title editing * * @method finishTitleEditing * @returns {List} */ finishTitleEditing: function() { var me = this; var $input = me.$header.find('input'); var oldTitle = me.$title.attr('data-old-title'); me.$title.html($input.val()).removeClass('hide').removeAttr('data-old-title'); $input.remove(); me.$header.removeClass('title-editing'); console.log(oldTitle, $input.val()); me._triggerEvent('titleChange', [me, oldTitle, $input.val()]); return me; }, /** * Cancel title editing * * @method cancelTitleEditing * @returns {List} */ cancelTitleEditing: function() { var me = this; var $input = me.$header.find('input'); if ($input.length === 0) { return me; } me.$title.html(me.$title.attr('data-old-title')).removeClass('hide'); $input.remove(); me.$header.removeClass('title-editing'); return me; }, /** * Remove list * * @method remove * @returns {List} - Just removed List instance */ remove: function() { var me = this; me.$lobiList.$lists.splice(me.$el.index(), 1); me.$elWrapper.remove(); return me; }, /** * Start editing of item * * @method editItem * @param {String} id - The id of the item to start updating * @returns {List} */ editItem: function(id) { var me = this; var $item = me.$lobiList.$el.find('li[data-id=' + id + ']'); var $form = $item.closest('.lobilist').find('.lobilist-add-todo-form'); var $footer = $item.closest('.lobilist').find('.lobilist-footer'); $form.removeClass('hide'); $footer.addClass('hide'); $form[0].id.value = $item.attr('data-id'); $form[0].title.value = $item.find('.lobilist-item-title').html(); $form[0].description.value = $item.find('.lobilist-item-description').html() || ''; $form[0].dueDate.value = $item.find('.lobilist-item-duedate').html() || ''; return me; }, /** * Suppress events. None of the events will be triggered until you call resumeEvents * @returns {List} */ suppressEvents: function() { this.eventsSuppressed = true; return this; }, /** * Resume all events. * @returns {List} */ resumeEvents: function() { this.eventsSuppressed = false; return this; }, _processItemData: function(item) { var me = this; return $.extend({}, me.$globalOptions.itemOptions, item); }, _createHeader: function() { var me = this; var $header = $('
', { 'class': 'lobilist-header' }); var $actions = $('
', { 'class': 'lobilist-actions' }).appendTo($header); if (me.$options.controls && me.$options.controls.length > 0) { if (me.$options.controls.indexOf('styleChange') > -1) { $actions.append(me._createDropdownForStyleChange()); } if (me.$options.controls.indexOf('edit') > -1) { $actions.append(me._createEditTitleButton()); $actions.append(me._createFinishTitleEditing()); $actions.append(me._createCancelTitleEditing()); } if (me.$options.controls.indexOf('add') > -1) { $actions.append(me._createAddNewButton()); } if (me.$options.controls.indexOf('remove') > -1) { $actions.append(me._createCloseButton()); } } me.$el.append($header); return $header; }, _createTitle: function() { var me = this; var $title = $('
', { 'class': 'lobilist-title', html: me.$options.title }).appendTo(me.$header); if (me.$options.controls && me.$options.controls.indexOf('edit') > -1) { $title.on('dblclick', function() { me.startTitleEditing(); }); } return $title; }, _createBody: function() { var me = this; return $('
', { 'class': 'lobilist-body' }).appendTo(me.$el); }, _createForm: function() { var me = this; var $form = $('
', { 'class': 'lobilist-add-todo-form hide' }); $('').appendTo($form); $('
').append( $('', { 'type': 'text', name: 'title', 'class': 'form-control', placeholder: 'TODO title' }) ).appendTo($form); $('
').append( $('