MediaWiki:Gadget-checkboxList-core.js: Difference between revisions

 
mNo edit summary
 
Line 1: Line 1:
/** <pre>
/** <pre>
  * Adds support for checkbox lists ([[Template:Checklist]])
  * highlightTable.js
  *
  *
* Description:
* Adds highlighting to tables
  *
  *
  * History:
  * History:
  * - 1.0: Original implementation - Cqm
  * - 1.0: Row highlighting                        - Quarenon
* - 1.1: Update from pengLocations.js v1.0      - Quarenon
* - 2.0: pengLocations v2.1, Granular cookie    - Saftzie
* - 2.1: Made compatible with jquery.tablesorter - Cqm
* - 2.2: Switch to localStorage                  - Cqm
* - 3.0: Allow cell highlighting                - mejrs
*
* @todo Allow the stored data to be coupled to the table in question. Currently the data is stored
*      on the page itself, so if any tables are shuffled, the highlighting doesn't follow. For
*      the same reason tables hosted on other pages are not synchronized.
  */
  */


/*
/**
  * DATA STORAGE STRUCTURE
  * DATA STORAGE STRUCTURE
  * ----------------------
  * ----------------------
Line 75: Line 86:
     onevar:true
     onevar:true
*/
*/
(function($, mw, OO, gs) {
     'use strict';
     'use strict';


        // constants
    // constants
     var STORAGE_KEY = 'gs:checkList',
     var STORAGE_KEY = 'gs:lightTable',
        LIST_CLASS = 'checklist',
         TABLE_CLASS = 'lighttable',
         CHECKED_CLASS = 'checked',
         LIGHT_ON_CLASS = 'highlight-on',
         NO_TOGGLE_PARENT_CLASS = 'no-toggle-parent',
         MOUSE_OVER_CLASS = 'highlight-over',
         INDEX_ATTRIBUTE = 'data-checklist-index',
         BASE_64_URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
         BASE_64_URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
         PAGE_SEPARATOR = '!',
         PAGE_SEPARATOR = '!',
         LIST_SEPARATOR = '.',
         TABLE_SEPARATOR = '.',
         CASTAGNOLI_POLYNOMIAL = 0x04c11db7,
         CASTAGNOLI_POLYNOMIAL = 0x04c11db7,
         UINT32_MAX = 0xffffffff,
         UINT32_MAX = 0xffffffff,
        conf = mw.config.get([
            'debug',
            'wgPageName'
        ]),


         self = {
         self = {
Line 104: Line 110:
             * Perform initial checks on the page and browser.
             * Perform initial checks on the page and browser.
             */
             */
             init: function () {
             init: function() {
                 var $lists = $(['ul.' + LIST_CLASS,
                 var $tables = $('table.' + TABLE_CLASS),
                                'div.' + LIST_CLASS + ' > ul'].join(', ')),
                     hashedPageName = self.hashString(mw.config.get('wgPageName'));
                     hashedPageName = self.hashString(mw.config.get('wgPageName'));


                 // check we have some tables to interact with
                 // check we have some tables to interact with
                 if (!$lists.length) {
                 if (!$tables.length) {
                     return;
                     return;
                 }
                 }
                 // check the browser supports local storage
                 // check the browser supports local storage
                 if (!rs.hasLocalStorage()) {
                 if (!gs.hasLocalStorage()) {
                     return;
                     return;
                 }
                 }


                 self.data = self.load(hashedPageName, $lists.length);
                 self.data = self.load(hashedPageName, $tables.length);
                 self.initLists(hashedPageName, $lists);
                 self.initTables(hashedPageName, $tables);
             },
             },


Line 127: Line 131:
             *
             *
             * @param hashedPageName The current page name as a hash.
             * @param hashedPageName The current page name as a hash.
             * @param $lists A list of checkbox lists on the current page.
             * @param $tables A list of highlightable tables on the current page.
             */
             */
             initLists: function (hashedPageName, $lists) {
             initTables: function(hashedPageName, $tables) {
                 $lists.each(function (listIndex) {
                 $tables.each(function(tIndex) {
                     var $this = $(this),
                     var $this = $(this),
                         toggleParent = !(
                         // data cells
                            $this.hasClass(NO_TOGGLE_PARENT_CLASS) ||
                        $cells = $this.find('td'),
                            $this.parent('div.' + LIST_CLASS).hasClass(NO_TOGGLE_PARENT_CLASS)
                        $rows = $this.find('tr:has(td)'),
                         ),
                         // don't rely on headers to find number of columns     
                         // list items
                         // count them dynamically
                         $items = $this.find('li'),
                         columns = 1,
                         listData = self.data[listIndex];
                         tableData = self.data[tIndex],
                        mode = 'cells';


                     // initialise list items if necessary
                     // Switching between either highlighting rows or cells
                     while ($items.length > listData.length) {
                     if (!$this.hasClass('individual')) {
                         listData.push(0);
                         mode = 'rows';
                        $cells = $rows;
                     }
                     }


                     $items.each(function (itemIndex) {
                     // initialise rows if necessary
                         var $this = $(this),
                    while ($cells.length > tableData.length) {
                            itemData = listData[itemIndex];
                         tableData.push(0);
                    }


                        // initialize checking based on the cookie
                    // counting the column count
                         self.setChecked($this, itemData);
                    // necessary to determine colspan of reset button
                    $rows.each(function() {
                         var $this = $(this);
                        columns = Math.max(columns, $this.children('th,td').length);
                    });


                        // give the item a unique index in the list
                    $cells.each(function(cIndex) {
                        $this.attr(INDEX_ATTRIBUTE, itemIndex);
                        var $this = $(this),
 
                            cellData = tableData[cIndex];
                        // set mouse events
                        $this
                            .click(function (e) {
                                var $this = $(this),
                                    $parent = $this.parent('ul').parent('li'),
                                    $childItems = $this.children('ul').children('li'),
                                    isChecked;


                                // don't bubble up to parent lists
                        // forbid highlighting any cells/rows that have class nohighlight
                                e.stopPropagation();
                        if (!$this.hasClass('nohighlight')) {
                            // initialize highlighting based on the cookie
                            self.setHighlight($this, cellData);


                                 function checkChildItems() {
                            // set mouse events
                                     var $this = $(this),
                            $this
                                        index = $this.attr(INDEX_ATTRIBUTE),
                                 .mouseover(function() {
                                        $childItems = $this.children('ul').children('li'),
                                     self.setHighlight($this, 2);
                                         childIsChecked = $this.hasClass(CHECKED_CLASS);
                                })
                                .mouseout(function() {
                                    self.setHighlight($this, tableData[cIndex]);
                                })
                                .click(function(e) {
                                    // don't toggle highlight when clicking links
                                    if ((e.target.tagName !== 'A') && (e.target.tagName !== 'IMG')) {
                                        // 1 -> 0
                                        // 0 -> 1
                                         tableData[cIndex] = 1 - tableData[cIndex];


                                    if (
                                         self.setHighlight($this, tableData[cIndex]);
                                         (isChecked && !childIsChecked) ||
                                         self.save(hashedPageName);
                                        (!isChecked && childIsChecked)
                                    ) {
                                        listData[index] = 1 - listData[index];
                                         self.setChecked($this, listData[index]);
                                     }
                                     }
 
                                 });
                                    if ($childItems.length) {
                        }
                                        $childItems.each(checkChildItems);
                                    }
                                }
 
                                function checkParent($parent) {
                                    var parentIndex = $parent.attr(INDEX_ATTRIBUTE),
                                        parentIsChecked = $parent.hasClass(CHECKED_CLASS),
                                        parentShouldBeChecked = true,
                                        $myParent = $parent.parent('ul').parent('li');
 
                                    $parent.children('ul').children('li').each(function () {
                                        var $child = $(this),
                                            childIsChecked = $child.hasClass(CHECKED_CLASS);
 
                                        if (!childIsChecked) {
                                            parentShouldBeChecked = false;
                                        }
                                    });
 
                                    if (
                                        (parentShouldBeChecked && !parentIsChecked && toggleParent) ||
                                        (!parentShouldBeChecked && parentIsChecked)
                                    ) {
                                        listData[parentIndex] = 1 - listData[parentIndex];
                                        self.setChecked($parent, listData[parentIndex]);
                                    }
 
                                    if ($myParent.length) {
                                        checkParent($myParent);
                                    }
                                }
 
                                // don't toggle highlight when clicking links
                                 if ((e.target.tagName !== 'A') && (e.target.tagName !== 'IMG')) {
                                    // 1 -> 0
                                    // 0 -> 1
                                    listData[itemIndex] = 1 - listData[itemIndex];
 
                                    self.setChecked($this, listData[itemIndex]);
                                    isChecked = $this.hasClass(CHECKED_CLASS);
 
                                    if ($childItems.length) {
                                        $childItems.each(checkChildItems);
                                    }
 
                                    // if the list has a parent
                                    // check if all the children are checked and uncheck the parent if not
                                    if ($parent.length) {
                                        checkParent($parent);
                                    }
 
                                    self.save(hashedPageName);
                                }
                            });
                     });
                     });


                    // TODO: figure out where to put this
                    /*
                     // add a button for reset
                     // add a button for reset
                     var button = new OO.ui.ButtonWidget( {
                     var button = new OO.ui.ButtonWidget({
                         label: 'Reset',
                         label: 'Clear selection',
                         icon: 'cancel',
                         icon: 'clear',
                         title: 'Resets all checkboxes in the list'
                         title: 'Removes all highlights from the table',
                        classes: ['ht-reset'] // this class is targeted by other gadgets, be careful removing it
                     });
                     });




                     button.$element.click(function () {
                     button.$element.click(function() {
                         $items.each(function (itemIndex) {
                         $cells.each(function(cIndex) {
                             listData[itemIndex] = 0;
                             tableData[cIndex] = 0;
                             self.setChecked($(this), 0);
                             self.setHighlight($(this), 0);
                         });
                         });


                         self.save(hashedPageName, $lists.length);
                         self.save(hashedPageName, $tables.length);
                     });
                     });
                     */
 
                     $this.append(
                        $('<tfoot>')
                            .append(
                                $('<tr>')
                                    .append(
                                        $('<th>')
                                            .attr('colspan', columns)
                                            .append(button.$element)
                                    )
                            )
                    );
                 });
                 });
             },
             },


             /*
             /*
             * Change the list item checkbox based on mouse events.
             * Change the cell background color based on mouse events.
             *
             *
             * @param $item The list item element.
             * @param $cell The cell element.
             * @param val The value to control what class to add (if any).
             * @param val The value to control what class to add (if any).
             *            0 -> unchecked (no class)
             *            0 -> light off (no class)
             *            1 -> light on
             *            1 -> light on
             *            2 -> mouse over
             *            2 -> mouse over
             */
             */
             setChecked: function ($item, val) {
             setHighlight: function($cell, val) {
                 $item.removeClass(CHECKED_CLASS);
                 $cell.removeClass(MOUSE_OVER_CLASS);
                $cell.removeClass(LIGHT_ON_CLASS);


                 switch (val) {
                 switch (val) {
                     // checked
                     // light on
                     case 1:
                     case 1:
                         $item.addClass(CHECKED_CLASS);
                         $cell.addClass(LIGHT_ON_CLASS);
                        break;
 
                    // mouse-over
                    case 2:
                        $cell.addClass(MOUSE_OVER_CLASS);
                         break;
                         break;
                 }
                 }
Line 284: Line 257:
             * @param hashedPageName A hash of the current page name.
             * @param hashedPageName A hash of the current page name.
             */
             */
             save: function (hashedPageName) {
             save: function(hashedPageName) {
                    // load the existing data so we know where to save it
                // load the existing data so we know where to save it
                 var curData = localStorage.getItem(STORAGE_KEY),
                 var curData = localStorage.getItem(STORAGE_KEY),
                     compressedData;
                     compressedData;
Line 312: Line 285:
             * @return the compressed data.
             * @return the compressed data.
             */
             */
             compress: function (data) {
             compress: function(data) {
                 var ret = {};
                 var ret = {};
               
 
                 Object.keys(data).forEach(function (hashedPageName) {
                 Object.keys(data).forEach(function(hashedPageName) {
                     var pageData = data[hashedPageName],
                     var pageData = data[hashedPageName],
                         pageKey = hashedPageName.charAt(0);
                         pageKey = hashedPageName.charAt(0);
Line 325: Line 298:
                     ret[pageKey][hashedPageName] = [];
                     ret[pageKey][hashedPageName] = [];


                     pageData.forEach(function (tableData) {
                     pageData.forEach(function(tableData) {
                         var compressedListData = '',
                         var compressedTableData = '',
                             i, j, k;
                             i, j, k;


Line 336: Line 309:
                             }
                             }


                             compressedListData += BASE_64_URL.charAt(k);
                             compressedTableData += BASE_64_URL.charAt(k);
                         }
                         }


                         ret[pageKey][hashedPageName].push(compressedListData);
                         ret[pageKey][hashedPageName].push(compressedTableData);
                     });
                     });


                     ret[pageKey][hashedPageName] = ret[pageKey][hashedPageName].join(LIST_SEPARATOR);
                     ret[pageKey][hashedPageName] = ret[pageKey][hashedPageName].join(TABLE_SEPARATOR);
                 });
                 });


                 Object.keys(ret).forEach(function (pageKey) {
                 Object.keys(ret).forEach(function(pageKey) {
                     var hashKeys = Object.keys(ret[pageKey]),
                     var hashKeys = Object.keys(ret[pageKey]),
                         hashedData = [];
                         hashedData = [];


                     hashKeys.forEach(function (key) {
                     hashKeys.forEach(function(key) {
                         var pageData = ret[pageKey][key];
                         var pageData = ret[pageKey][key];
                         hashedData.push(key + pageData);
                         hashedData.push(key + pageData);
Line 365: Line 338:
             *
             *
             * @param hashedPageName A hash of the current page name.
             * @param hashedPageName A hash of the current page name.
             * @param numLists The number of lists on the current page. Used to ensure the loaded
             * @param numTables The number of tables on the current page. Used to ensure the loaded
             *                 data matches the number of lists on the page thus handling cases
             *                 data matches the number of tables on the page thus handling cases
             *                 where lists have been added or removed. This does not check the
             *                 where tables have been added or removed. This does not check the
             *                 amount of items in the given lists.
             *                 amount of rows in the given tables.
             *
             *
             * @return The data for the current page.
             * @return The data for the current page.
             */
             */
             load: function (hashedPageName, numLists) {
             load: function(hashedPageName, numTables) {
                 var data = localStorage.getItem(STORAGE_KEY),
                 var data = localStorage.getItem(STORAGE_KEY),
                     pageData;
                     pageData;
Line 389: Line 362:
                 }
                 }


                 // if more lists were added
                 // if more tables were added
                 // add extra arrays to store the data in
                 // add extra arrays to store the data in
                 // also populates if no existing data was found
                 // also populates if no existing data was found
                 while (numLists > pageData.length) {
                 while (numTables > pageData.length) {
                     pageData.push([]);
                     pageData.push([]);
                 }
                 }


                 // if lists were removed, remove data from the end of the list
                 // if tables were removed, remove data from the end of the list
                 // as there's no way to tell which was removed
                 // as there's no way to tell which was removed
                 while (numLists < pageData.length) {
                 while (numTables < pageData.length) {
                     pageData.pop();
                     pageData.pop();
                 }
                 }
Line 413: Line 386:
             * @return the parsed data.
             * @return the parsed data.
             */
             */
             parse: function (data) {
             parse: function(data) {
                 var ret = {};
                 var ret = {};


                 Object.keys(data).forEach(function (pageKey) {
                 Object.keys(data).forEach(function(pageKey) {
                     var pageData = data[pageKey].split(PAGE_SEPARATOR);
                     var pageData = data[pageKey].split(PAGE_SEPARATOR);


                     pageData.forEach(function (listData) {
                     pageData.forEach(function(tableData) {
                         var hashedPageName = listData.substr(0, 8);
                         var hashedPageName = tableData.substr(0, 8);


                         listData = listData.substr(8).split(LIST_SEPARATOR);
                         tableData = tableData.substr(8).split(TABLE_SEPARATOR);
                         ret[hashedPageName] = [];
                         ret[hashedPageName] = [];


                         listData.forEach(function (itemData, index) {
                         tableData.forEach(function(rowData, index) {
                             var i, j, k;
                             var i, j, k;


                             ret[hashedPageName].push([]);
                             ret[hashedPageName].push([]);


                             for (i = 0; i < itemData.length; i += 1) {
                             for (i = 0; i < rowData.length; i += 1) {
                                 k = BASE_64_URL.indexOf(itemData.charAt(i));
                                 k = BASE_64_URL.indexOf(rowData.charAt(i));


                                 // input validation
                                 // input validation
Line 458: Line 431:
             * @return the result of the hash.
             * @return the result of the hash.
             */
             */
             hashString: function (input) {
             hashString: function(input) {
                 var ret = 0,
                 var ret = 0,
                     table = [],
                     table = [],
Line 495: Line 468:
         };
         };


     // disable for debugging
     $(self.init);
    if (!(['User:Cqm/Scrapbook_4'].indexOf(conf.wgPageName) && conf.debug)) {
        $(self.init);
    }


     /*
     /*
Line 531: Line 501:
     console.log(parsedData);
     console.log(parsedData);
     */
     */
/* </pre> */
 
}(this.jQuery, this.mediaWiki, this.OO, this.gswiki));
 
// </pre>