/** * Created by asantoni on 11/09/15. */ var AIRTIME = (function (AIRTIME) { //Module initialization if (AIRTIME.widgets === undefined) { AIRTIME.widgets = {}; } //Table widget constructor /** * * * @param wrapperDOMNode * @param {boolean} bItemSelection * @param {Object} toolbarButtons * @param {Object} dataTablesOptions * @param {Object} [emptyPlaceholder] * @param {string} emptyPlaceholder.html * @param {string} emptyPlaceholder.iconClass * * @returns {Table} * @constructor */ var Table = function ( wrapperDOMNode, bItemSelection, toolbarButtons, dataTablesOptions, emptyPlaceholder ) { var self = this; self.HUGE_INT = Math.pow(2, 53) - 1; //Constants and enumerations self.SELECTION_MODE = { SINGLE: 0, MULTI_SHIFT: 1, MULTI_CTRL: 2, }; //Member variables self._datatable = null; self._selectedRows = []; //An array containing the underlying objects for each selected row. (Easy to use!) //self._selectedRowVisualIdxMap = []; //A map of the visual index of a selected rows onto the actual row data. self._selectedRowVisualIdxMin = self.HUGE_INT; self._selectedRowVisualIdxMax = -1; self._$wrapperDOMNode = null; self._toolbarButtons = null; //Save some of the constructor parameters self._$wrapperDOMNode = $(wrapperDOMNode); self._toolbarButtons = toolbarButtons; self._emptyPlaceholder = emptyPlaceholder; // Exclude the leftmost column if we're implementing item selection self._colVisExcludeColumns = bItemSelection ? [0] : []; //Finish initialization of the datatable since everything is declared by now. // If selection is enabled, add in the checkbox column. if (bItemSelection) { dataTablesOptions["aoColumns"].unshift( /* Checkbox */ { sTitle: "", mData: self._datatablesCheckboxDataDelegate.bind(this), bSortable: false, bSearchable: false, sWidth: "24px", sClass: "airtime_table_checkbox", } ); } var options = { aoColumns: [ /* Title */ { sTitle: $.i18n._("Make sure to override me"), mDataProp: "track_title", sClass: "library_title", sWidth: "170px", }, ], bProcessing: true, bServerSide: true, sAjaxSource: baseUrl + "rest/media", //Override me sAjaxDataProp: "aaData", bScrollCollapse: false, deferLoading: 1, //0 tells it there's zero elements loaded and disables the automatic AJAX. We don't want to load until after we bind all our event handlers, to prevent a race condition with the "init" event callback. sPaginationType: "full_numbers", bJQueryUI: true, bAutoWidth: false, aaSorting: [], iDisplayLength: 25, aLengthMenu: [25, 50, 100], oLanguage: getDatatablesStrings({ sEmptyTable: $.i18n._(""), sZeroRecords: $.i18n._("No matching results found."), }), oColVis: { sAlign: "right", aiExclude: self._colVisExcludeColumns, buttonText: $.i18n._("Columns"), iOverlayFade: 0, }, // z = ColResize, R = ColReorder, C = ColVis sDom: 'Rf<"dt-process-rel"r><"H"<"table_toolbar"C>><"dataTables_scrolling"t<".empty_placeholder"<".empty_placeholder_image"><".empty_placeholder_text">>><"F"lip>>', fnPreDrawCallback: function () { $("#draggingContainer").remove(); }, fnServerData: self._fetchData.bind(self), //"fnInitComplete" : function() { self._setupEventHandlers(bItemSelection) } fnDrawCallback: function () { self.clearSelection(); }, }; //Override any options with those passed in as arguments to this constructor. for (var key in dataTablesOptions) { options[key] = dataTablesOptions[key]; } if (options.fnCreatedRow) { options.fnCreatedRow = options.fnCreatedRow.bind(self); } if (options.fnDrawCallback) { options.fnDrawCallback = options.fnDrawCallback.bind(self); } self._datatable = self._$wrapperDOMNode.dataTable(options); // self._datatable.fnDraw(); //Load the AJAX data now that our event handlers have been bound. self._setupEventHandlers(bItemSelection); //return self._datatable; return self; }; Table.prototype.assignDblClickHandler = function (fn) { $(this._datatable, "tbody tr").on( "dblclick", this._SELECTORS.SELECTION_TABLE_ROW, fn ); }; /* Set up global event handlers for the datatable. * @param bItemSelection Whether or not row selection behaviour should be enabled for this widget. * */ Table.prototype._setupEventHandlers = function (bItemSelection) { var self = this; /** This table row event handler is created once and catches events for any row. (It's less resource intensive * than having a per-row callback...) */ if (bItemSelection) { $(self._datatable, "tbody tr").on( "click contextmenu", self._SELECTORS.SELECTION_TABLE_ROW, function (e) { var aData = self._datatable.fnGetData(this); var iDisplayIndex = $(this).index(); // The index of the row in the current page in the table. var nRow = this; e.stopPropagation(); e.preventDefault(); document.getSelection().removeAllRanges(); var selectionMode = self.SELECTION_MODE.SINGLE; if (e.shiftKey) { selectionMode = self.SELECTION_MODE.MULTI_SHIFT; } else if (e.ctrlKey) { selectionMode = self.SELECTION_MODE.MULTI_CTRL; } if (e.button == 2) { selectionMode = self.SELECTION_MODE.SINGLE; } self.selectRow(nRow, aData, selectionMode, iDisplayIndex); } ); $(self._datatable, "tbody tr").on( "click", self._SELECTORS.SELECTION_CHECKBOX, function (e) { $this = $(this); var iVisualRowIdx = $this.parent().index(); var aData = self._datatable.fnGetData(iVisualRowIdx); var selectionMode = self.SELECTION_MODE.MULTI_CTRL; //Behaviour for checkboxes. if (e.shiftKey) { selectionMode = self.SELECTION_MODE.MULTI_SHIFT; } self.selectRow($this.parent(), aData, selectionMode, iVisualRowIdx); //Always multiselect for checkboxes e.stopPropagation(); return true; } ); // Clear selection when switching pages $(self._datatable).on("page", function () { self.clearSelection(); }); } // On filter, display the number of total and filtered results in the search bar $(self._datatable).on("filter", function () { var dt = self._datatable, f = dt.closest(".dataTables_wrapper").find(".filter-message"), totalRecords = dt.fnSettings().fnRecordsTotal(), totalDisplayRecords = dt.fnSettings().fnRecordsDisplay(); if (f.length === 0) { var el = document.createElement("span"); el.setAttribute("class", "filter-message"); f = dt .closest(".dataTables_wrapper") .find(".dataTables_filter") .append(el) .find(".filter-message"); } f.text( totalRecords > totalDisplayRecords ? $.i18n._("Filtering out ") + (totalRecords - totalDisplayRecords) + $.i18n._(" of ") + totalRecords + $.i18n._(" records") : "" ); dt.closest(".dataTables_wrapper") .find('.dataTables_filter input[type="text"]') .css("padding-right", f.outerWidth()); }); //Since this function is already called when the datatables initialization is complete, we know the DOM //structure for the datatable exists and can just proceed to setup the toolbar DOM elements now. self._setupToolbarButtons(self._toolbarButtons); }; /** * Member functions * */ /** Populate the toolbar with buttons. * * @param buttons A list of objects which contain button definitions. See self.TOOLBAR_BUTTON_ROLES for an example, or use getStandardToolbarButtons() to get a list of them. * @private */ Table.prototype._setupToolbarButtons = function (buttons) { var self = this; var $menu = self._$wrapperDOMNode .parent() .parent() .find("div.table_toolbar"); $menu.addClass("btn-toolbar"); //Create the toolbar buttons. $.each(buttons, function (idx, btn) { var buttonElement = self._createToolbarButton( btn.title, btn.iconClass, btn.extraBtnClass, btn.elementId ); $menu.append(buttonElement); btn.element = buttonElement; //Save this guy in case you need it later. //Bind event handlers to each button $.each(btn.eventHandlers, function (eventName, eventCallback) { $(buttonElement).on(eventName, function () { if ($(buttonElement).find("button").is(":disabled")) { return; } eventCallback(); }); }); }); self._checkToolbarButtons(); }; /** * Check each of the toolbar buttons for the table and disable them if their constraints are invalid. * * Passes current Table object context to function calls. */ Table.prototype._checkToolbarButtons = function () { var self = this; $.each(self._toolbarButtons, function (idx, btn) { var btnNode = $(btn.element).find("button").get(0); btnNode.disabled = btn.disabled = !btn.validateConstraints.call(self); }); }; /** Create the DOM element for a toolbar button and return it. */ Table.prototype._createToolbarButton = function ( title, iconClass, extraBtnClass, elementId ) { if (!iconClass) { iconClass = "icon-plus"; } // var title = $.i18n._('Delete'); var outerDiv = document.createElement("div"); outerDiv.className = "btn-group"; outerDiv.title = title; var innerButton = document.createElement("button"); //innerButton.className = 'btn btn-small ' + extraBtnClass; innerButton.className = "btn " + extraBtnClass; innerButton.id = elementId; var innerIcon = document.createElement("i"); innerIcon.className = "icon-white " + iconClass; var innerTextSpan = document.createElement("span"); var innerText = document.createTextNode(title); innerTextSpan.appendChild(innerText); innerButton.appendChild(innerIcon); innerButton.appendChild(innerTextSpan); outerDiv.appendChild(innerButton); /* Here's an example of what the button HTML should look like: "