ResourcesSelected Plugin working with URNs - http://trac.myslice.info/ticket/36
[myslice.git] / plugins / resources_selected / static / js / resources_selected.js
index 77d023c..9a06447 100644 (file)
  * License: GPLv3
  */
 
-/*
- * It's a best practice to pass jQuery to an IIFE (Immediately Invoked Function
- * Expression) that maps it to the dollar sign so it can't be overwritten by
- * another library in the scope of its execution.
- */
 (function( $ ){
 
-    var PLUGIN_NAME = 'ResourcesSelected';
-
-    // Routing calls
-    jQuery.fn.ResourcesSelected = function( method ) {
-        if ( methods[method] ) {
-            return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
-        } else if ( typeof method === 'object' || ! method ) {
-            return methods.init.apply( this, arguments );
-        } else {
-            jQuery.error( 'Method ' +  method + ' does not exist on jQuery.' + PLUGIN_NAME );
-        }    
-
-    };
-
-    /***************************************************************************
-     * Public methods
-     ***************************************************************************/
-
-    var methods = {
-
-        /**
-         * @brief Plugin initialization
-         * @param options : an associative array of setting values
-         * @return : a jQuery collection of objects on which the plugin is
-         *     applied, which allows to maintain chainability of calls
-         */
-        init : function( options ) {
-
-            return this.each(function(){
-
-                var $this = $(this);
-
-                /* An object that will hold private variables and methods */
-                var plugin = new ResourcesSelected(options);
-                $(this).data('Manifold', plugin);
-
-                //$this.set_query_handler(options.query_uuid, hazelnut.query_handler);
-                //$this.set_record_handler(options.query_uuid, hazelnut.record_handler); 
-
-                //var RESULTS_RESOURCES = '/results/' + options.resource_query_uuid + '/changed';
-                //var UPDATE_RESOURCES  = '/update-set/' + options.resource_query_uuid;
-                //$.subscribe(RESULTS_RESOURCES, function(e, resources) { s.set_resources(resources);    });
-                //$.subscribe(UPDATE_RESOURCES,  function(e, resources, change) { s.update_resources(resources, change); });
-                
-            }); // this.each
-        }, // init
-
-        /**
-         * @brief Plugin destruction
-         * @return : a jQuery collection of objects on which the plugin is
-         *     applied, which allows to maintain chainability of calls
-         */
-        destroy : function( ) {
-
-            return this.each(function() {
-                var $this = $(this);
-                var plugin = $this.data('Manifold');
-
-                // Remove associated data
-                plugin.remove();
-                $this.removeData('Manifold');
-            });
-        }, // destroy
-
-    }; // var methods
-
-    /***************************************************************************
-     * Plugin object
-     ***************************************************************************/
-
-    function ResourcesSelected(options)
-    {
-        /* member variables */
-        this.options = options;
-        var object = this;
-
-        /* The resources that are in the slice */
-        this.current_resources = null;
-
-        /* The resources that are in the slice before any edit */
-        this.initial_resources = null;
-
-        var rs = this;
-
-        /* constructor */
-           // ioi: resources table id
-        var TABLE_NAME = '#table-' + options.plugin_uuid;
-        this.table = $(TABLE_NAME).dataTable({
-            //sPaginationType: 'full_numbers',  // Use pagination
-            sPaginationType: 'bootstrap',
-            //bJQueryUI: true,
-            //bRetrieve: true,
-            sScrollX: '100%',                 // Horizontal scrolling 
-            bSortClasses: false,              // Disable style for the sorted column
-            aaSorting: [[ 1, "asc" ]],        // Default sorting on URN
-            fnDrawCallback: function() {      // Reassociate close click every time the table is redrawn
-                /* Prevent to loop on click while redrawing table  */
-                jQuery('.ResourceSelectedClose').unbind('click');
-                /* Handle clicks on close span */
-                /* Reassociate close click every time the table is redrawn */
-                $('.ResourceSelectedClose').bind('click',{instance: rs}, object.close_click);
-            }
-         });
+    // XXX record selected (multiple selection ?)
+    // XXX record disabled ?
+    // XXX out of sync plugin ?
+    // XXX out of date record ?
+    // record tags ???
+    //
+    // criticality of the absence of a handler in a plugin
+    // non-critical only can have switch case
+    // 
+    // Record state through the query cycle
+
 
-        /* methods */
+    var ResourcesSelected = Plugin.extend({
 
-        this.set_resources = function(resources)
+        init: function(options, element)
         {
-            console.log("set_resources");
-            /* Some sanity checks on the API results */
-            if(resources.length==0){
-                this.table.html(errorDisplay("No Result"));   
-                return;
-            }
+            this._super(options, element);
+
+            var self = this;
+            this.table = this.elmt('table').dataTable({
+// the original hazelnut layout was
+//                sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
+// however the bottom line with 'showing blabla...' and the navigation widget are not really helpful
+                sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t>",
+// so this does not matter anymore now that the pagination area is turned off
+//                sPaginationType: 'bootstrap',
+               bAutoWidth: true,
+//                bJQueryUI      : true,
+//                bRetrieve      : true,
+//                sScrollX       : '100%',                 // Horizontal scrolling 
+//                bSortClasses   : false,              // Disable style for the sorted column
+//                aaSorting      : [[ 0, 'asc' ]],        // Default sorting on URN
+//                fnDrawCallback: function() {      // Reassociate close click every time the table is redrawn
+//                    /* Prevent to loop on click while redrawing table  */
+//                    $('.ResourceSelectedClose').unbind('click');
+//                    /* Handle clicks on close span */
+//                    /* Reassociate close click every time the table is redrawn */
+//                    $('.ResourceSelectedClose').bind('click', self, self._close_click);
+//                }
+             });
+            
+            // XXX This should not be done at init...
+            this.elmt('update').click(this, this.do_update);
+            this.elmt('refresh').click(this, this.do_refresh);
+            this.elmt('reset').click(this, this.do_reset);
+            this.elmt('clear_annotations').click(this, this.do_clear_annotations);
 
-            if (typeof(resources[0].error) != 'undefined') {
-                this.table.html(errorDisplay(resources[0].error));
-                return;
-            }
+            this.listen_query(options.query_uuid);
+        },
+
+        /*************************** PLUGIN EVENTS ****************************/
+
+        /***************************** GUI EVENTS *****************************/
+
+        do_update: function(e) 
+        {
+            var self = e.data;
+            // XXX check that the query is not disabled
+            manifold.raise_event(self.options.query_uuid, RUN_UPDATE);
+        },
+
+       // related buttons are also disabled in the html template
+        do_refresh: function(e)
+        {
+            throw 'resource_selected.do_refresh Not implemented';
+        },
+
+        do_reset: function(e)
+        {
+            throw 'resources_selected.do_reset Not implemented';
+        },
+
+        do_clear_annotations: function(e)
+        {
+            throw 'resources_selected.do_clear_annotations Not implemented';
+        },
+        
+        /************************** GUI MANIPULATION **************************/
+
+        
+        set_button_state: function(name, state)
+        {
+            this.elmt(name).attr('disabled', state ? false : 'disabled');
+        },
+
+        clear: function()
+        {
 
-            /* Update the table with resources in the slice */
-            //var slivers = $.grep(resources, function(i) {return typeof(i['sliver']) != 'undefined';})
-            var slivers = resources;
-            var sliver_urns = Array();
-            // ioi : refubrished
-               $.each(resources, function(i, x) { sliver_urns.push({urn:x.urn, timeslot:"0"}); }); // ioi
+        },
 
-            this.initial_resources = sliver_urns; // We make a copy of the object // ioi
-               // ioi
+        find_row: function(key)
+        {
+            // key in third position, column id = 2
+            var KEY_POS = 2;
+
+            var cols = $.grep(this.table.fnSettings().aoData, function(col) {
+                return (col._aData[KEY_POS] == key);
+            } );
+
+            if (cols.length == 0)
+                return null;
+            if (cols.length > 1)
+                throw "Too many same-key rows in ResourceSelected plugin";
+
+            return cols[0];
+        },
+
+        set_state: function(data)
+        {
+            var action;
+            var msg;
+            var button = '';
+
+            var row;
+           
+           // make sure the change is visible : toggle on the whole plugin
+           // this might hae to be made an 'auto-toggle' option of this plugin..
+           // also it might be needed to be a little finer-grained here
+           this.toggle_on();
            
-            if (this.current_resources == null) {
-                this.current_resources = sliver_urns;
-
-                /* We simply add to the ResourceSelected table */
-                var newlines=Array();
-                $.each(sliver_urns, function(index, elt) {
-                    newlines.push(Array('attached', elt.urn, elt.timeslot, "<span class='ui-icon ui-icon-close ResourceSelectedClose' id='"+elt.urn+"'/>")); // ioi: added last element
-                });
-                this.table.dataTable().fnAddData(newlines);
+            switch(data.request) {
+                case FIELD_REQUEST_ADD_RESET:
+                case FIELD_REQUEST_REMOVE_RESET:
+                    // find line and delete it
+                    row = this.find_row(data.value);
+                    if (row)
+                        this.table.fnDeleteRow(row.nTr);
+                    return;
+                case FIELD_REQUEST_CHANGE:
+                    action = 'UPDATE';
+                    break;
+                case FIELD_REQUEST_ADD:
+                    action = 'ADD';
+                    break;
+                case FIELD_REQUEST_REMOVE:
+                    action = 'REMOVE';
+                    break;
+            }
+
+            switch(data.status) {
+                case FIELD_REQUEST_PENDING:
+                    msg   = 'PENDING';
+                    button = "<span class='glyphicon glyphicon-remove ResourceSelectedClose' id='" + data.key + "'/>";
+                    break;
+                case FIELD_REQUEST_SUCCESS:
+                    msg   = 'SUCCESS';
+                    break;
+                case FIELD_REQUEST_FAILURE:
+                    msg   = 'FAILURE';
+                    break;
+            }
+
+            var status = msg + status;
+
+            // find line
+            // if no, create it, else replace it
+            // XXX it's not just about adding lines, but sometimes removing some
+            // XXX how do we handle status reset ?
+            row = this.find_row(data.value);
+            newline = [
+                action,
+                data.key,
+                data.value,
+                msg,
+                button
+            ];
+            if (!row) {
+                // XXX second parameter refresh = false can improve performance. todo in hazelnut also
+                this.table.fnAddData(newline);
+                row = this.find_row(data.value);
             } else {
-                alert('Slice updated. Refresh not yet implemented!');
+                // Update row text...
+                this.table.fnUpdate(newline, row.nTr);
             }
-        }
 
-        this.update_resources = function(resources, change) {
+            // Change cell color according to status
+            if (row) {
+                $(row.nTr).removeClass('add remove')
+                var cls = action.toLowerCase();
+                if (cls)
+                    $(row.nTr).addClass(cls);
+            }
+        },
+
+        /*************************** QUERY HANDLER ****************************/
+
+        // NONE
+
+        /*************************** RECORD HANDLER ***************************/
+
+        on_new_record: function(record)
+        {
+            // if (not and update) {
+
+                // initial['resource'], initial['lease'] ?
+                this.initial.push(record.urn);
+
+                // We simply add to the table
+            // } else {
+                //                 \ this.initial_resources
+                //                  \
+                // this.             \
+                // current_resources  \    YES    |   NO
+                // --------------------+----------+---------
+                //       YES           | attached | added
+                //       NO            | removed  |   /
+                // 
+
+            // }
+        },
+
+        // QUERY STATUS
+        //                                      +-----------------+--------------+
+        //                                      v        R        |              |
+        // +-------+  ?G    +-------+        +-------+        +---+---+          |
+        // |       | -----> |       |  !G    |       |        |       |    DA    |
+        // |  ND   |        |  PG   | -----> |   D   | -----> |  PC   | <------+ |
+        // |       | <----- |       |  ~G    |       |   C    |       |        | | 
+        // +-------+   GE   +-------+        +-------+        +-------+      +------+
+        //                                       ^              ^  |         |      |
+        //                                       | DA        UE |  | ?U      | PCA  |
+        //                                       |              |  v         |      |
+        //                                   +-------+        +-------+      +------+
+        //                                   |       |   !U   |       |         ^
+        //                                   |  AD   | <----- |  PU   | --------+
+        //                                   |       |        |       |   ~U     
+        //                                   +-------+        +-------+          
+        //                                                                       
+        //
+        // LEGEND:
+        // 
+        // Plugins (i) receive state information, (ii) perform actions
+        //
+        // States:                                  Actions:
+        // ND : No data                             ?G : Get query
+        // PG : Pending Get                         !G : Get reply  
+        //  D : Data present                        ~G : Get partial reply
+        // PC : Pending changes                     GE : Get error                            
+        // PU : Pending update                       C : Change request
+        // PCA: Pending change with annotation       R : Reset request
+        // AD : Annotated data                      ?U : Update query
+        //                                          !U : Update reply
+        //                                          ~U : Update partial reply
+        //                                          UE : Update error            
+        //                                          DA : Delete annotation request
+        // NOTE:
+        // - D -> PU directly if the user chooses 'dynamic update'
+        // - Be careful for updates if partial Get success
+
+        // ND: No data == initialization state
+        
+        // PG : Pending get
+        // - on_query_in_progress
+        // NOTE: cannot distinguish get and update here. Is it important ?
+
+        on_query_in_progress: function()
+        {
+            this.spin();
+        },
+
+        // D : Data present
+        // - on_clear_records (Get)
+        // - on_new_record (shared with AD) XXX
+        // - on_query_done
+        // NOTE: cannot distinguish get and update here. Is it important ?
+        // NOTE: Would record key be sufficient for update ?
+
+        on_clear_records: function()
+        {
+            this.clear();
+        },
+
+        on_new_record: function(record)
+        {
+        },
+
+        on_query_done: function()
+        {
+            this.unspin();
+        },
+
+        // PC : Pending changes
+        // NOTE: record_key could be sufficient 
+        on_added_record: function(record)
+        {
+            this.set_record_state(record, RECORD_STATE_ADDED);
+        },
+
+        on_removed_record: function(record_key)
+        {
+            this.set_record_state(RECORD_STATE_REMOVED);
+        },
+
+        // PU : Pending update
+        // - on_query_in_progress (already done)
+        
+        // PCA : Pending change with annotation
+        // NOTE: Manifold will inform the plugin about updates, and thus won't
+        // call new record, even if the same channel UUID is used...
+        // - TODO on_updated_record
+        // - Key and confirmation could be sufficient, or key and record state
+        // XXX move record state to the manifold plugin API
+
+        on_field_state_changed: function(result)
+        {
+            console.log(result)
+            /* this.set_state(result.request, result.key, result.value, result.status); */
+            this.set_state(result);
+        },
+
+        // XXX we will have the requests for change
+        // XXX + the requests to move into the query cycle = the buttons aforementioned
+
+        // XXX what happens in case of updates ? not implemented yet
+        // XXX we want resources and leases
+        // listen for SET_ADD and SET_REMOVE for slice query
+
+        /************************** PRIVATE METHODS ***************************/
+
+        _close_click: function(e)
+        {
+            var self = e.data;
+
+            //jQuery.publish('selected', 'add/'+key_value);
+            // this.parentNode is <td> this.parentNode.parentNode is <tr> 
+            // this.parentNode.parentNode.firstChild is the first cell <td> of this line <tr>
+            // this.parentNode.parentNode.firstChild.firstChild is the text in that cell
+            //var firstCellVal=this.parentNode.parentNode.firstChild.firstChild.data;
+            var remove_urn = this.id; 
+            var current_resources = event.data.instance.current_resources;
+            var list_resources = $.grep(current_resources, function(x) {return x.urn != remove_urn});
+            //jQuery.publish('selected', 'cancel/'+this.id+'/'+unfold.get_value(firstCellVal));
+            $.publish('/update-set/' + event.data.instance.options.resource_query_uuid, [list_resources, true]);
+        },
+
+        /******************************** TODO ********************************/
+
+        update_resources: function(resources, change)
+        {
             console.log("update_resources");
             var my_oTable = this.table.dataTable();
             var prev_resources = this.current_resources; 
-            /*      \ this.initial_resources
-             *           \
-             * this.          \
-             * current_resources  \    YES    |   NO
-             * --------------------+----------+---------
-             *       YES           | attached | added
-             *       NO            | removed  |   /
-             */
 
             /*
              * The first time the query is advertised, don't do anything.  The
                 var urn = r.urn;
                 time = r.timeslot;
                               
-                var SPAN = "<span class='ui-icon ui-icon-close ResourceSelectedClose' id='"+urn+"'/>";
+                var SPAN = "<span class='glyphicon glyphicon-remove ResourceSelectedClose' id='"+urn+"'/>";
                 var slot = "<span id='resource_"+urn+"'>" + time + "</span>"; //ioi
                 // ioi
                 var newline=Array();
                 var node = r.urn;
                 time = r.timeslot;
 
-                var SPAN = "<span class='ui-icon ui-icon-close ResourceSelectedClose' id='"+node+"'/>";
+                var SPAN = "<span class='glyphicon glyphicon-renomve ResourceSelectedClose' id='"+node+"'/>";
                 var slot = "<span id='resource_"+node+"'>" + time + "</span>"; //ioi
                 // ioi
                 var newline=Array();
                 var node = r.urn;
                 var time = r.timeslot;
                     
-                var SPAN = "<span class='ui-icon ui-icon-close ResourceSelectedClose' id='"+node+"'/>";
+                var SPAN = "<span class='glyphicon glyphicon-remove ResourceSelectedClose' id='"+node+"'/>";
                 var slot = "<span id='resource_"+node+"'>" + time + "</span>";
                 // ioi
                 var newline=Array();
              /* Allow the user to update the slice */
              //jQuery('#updateslice-' + data.ResourceSelected.plugin_uuid).prop('disabled', false);
 
-        } // update_resources
+        }, // update_resources
 
-        this.record_handler = function(e, event_type, record)
-        {
-            // elements in set
-            switch(event_type) {
-                case NEW_RECORD:
-                    /* NOTE in fact we are doing a join here */
-                    if (object.received_all)
-                        // update checkbox for record
-                        object.set_checkbox(record);
-                    else
-                        // store for later update of checkboxes
-                        object.in_set_buffer.push(record);
-                    break;
-                case CLEAR_RECORDS:
-                    // nothing to do here
-                    break;
-                case IN_PROGRESS:
-                    manifold.spin($(this));
-                    break;
-                case DONE:
-                    if (object.received_all)
-                        manifold.spin($(this), false);
-                    object.received_set = true;
-                    break;
-            }
-        };
+    });
 
-        this.record_handler_all = function(e, event_type, record)
-        {
-            // all elements
-            switch(event_type) {
-                case NEW_RECORD:
-                    // Add the record to the table
-                    object.new_record(record);
-                    break;
-                case CLEAR_RECORDS:
-                    object.table.fnClearTable();
-                    break;
-                case IN_PROGRESS:
-                    manifold.spin($(this));
-                    break;
-                case DONE:
-                    if (object.received_set) {
-                        /* XXX needed ? XXX We uncheck all checkboxes ... */
-                        $("[id^='datatables-checkbox-" + object.options.plugin_uuid +"']").attr('checked', false);
-
-                        /* ... and check the ones specified in the resource list */
-                        $.each(object.in_set_buffer, function(i, record) {
-                            object.set_checkbox(record);
-                        });
-
-                        manifold.spin($(this), false);
-                    }
-                    object.received_all = true;
-                    break;
-            }
-        };
-
-        this.query_handler = function(e, event_type, data)
-        {
-            // This replaces the complex set_query function
-            // The plugin does not need to remember the query anymore
-            switch(event_type) {
-                // Filters
-                case FILTER_ADDED:
-                case FILTER_REMOVED:
-                case CLEAR_FILTERS:
-                    // XXX Here we might need to maintain the list of filters !
-                    /* Process updates in filters / current_query must be updated before this call for filtering ! */
-                    object.table.fnDraw();
-                    break;
-
-                // Fields
-                /* Hide/unhide columns to match added/removed fields */
-                case FIELD_ADDED:
-                    var field = data;
-                    var oSettings = object.table.fnSettings();
-                    var cols = oSettings.aoColumns;
-                    var index = object.getColIndex(field,cols);
-                    if(index != -1)
-                        object.table.fnSetColumnVis(index, true);
-                    break;
-                case FIELD_REMOVED:
-                    var field = data;
-                    var oSettings = object.table.fnSettings();
-                    var cols = oSettings.aoColumns;
-                    var index = object.getColIndex(field,cols);
-                    if(index != -1)
-                        object.table.fnSetColumnVis(index, false);
-                    break;
-                case CLEAR_FIELDS:
-                    alert('Hazelnut::clear_fields() not implemented');
-                    break;
-            } // switch
-        }
-
-    } // ResourcesSelected
-
-
-    /***************************************************************************
-     * Private methods
-     ***************************************************************************/
-
-    /* Callbacks */    
-    function close_click(event){
-        //jQuery.publish('selected', 'add/'+key_value);
-        // this.parentNode is <td> this.parentNode.parentNode is <tr> 
-        // this.parentNode.parentNode.firstChild is the first cell <td> of this line <tr>
-        // this.parentNode.parentNode.firstChild.firstChild is the text in that cell
-        //var firstCellVal=this.parentNode.parentNode.firstChild.firstChild.data;
-        var remove_urn = this.id; 
-        var current_resources = event.data.instance.current_resources;
-        var list_resources = $.grep(current_resources, function(x) {return x.urn != remove_urn});
-        //jQuery.publish('selected', 'cancel/'+this.id+'/'+get_value(firstCellVal));
-        $.publish('/update-set/' + event.data.instance.options.resource_query_uuid, [list_resources, true]);
-    }
+    $.plugin('ResourcesSelected', ResourcesSelected);
 
 })(jQuery);