<commit_before>
/**
 * shows a dialog to get the table exported
 * new ExportTableDialog({
 *  table: table_model
 * })
 *
 */
cdb.admin.ExportTableDialog = cdb.admin.BaseDialog.extend({

  // _CSV_FILTER: "SELECT *, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_X(the_geom) ELSE ST_X(ST_PointOnSurface(the_geom)) END as longitude, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_Y(the_geom) ELSE ST_Y(ST_PointOnSurface(the_geom)) END as latitude, ST_AsGeoJSON(the_geom,5) as geojson FROM (%%sql%%) as subq",
  _CSV_FILTER: "SELECT *, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_X(the_geom) WHEN ST_IsEmpty(ST_PointOnSurface(the_geom)) THEN null ELSE ST_X(ST_PointOnSurface(the_geom)) END as longitude_0, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_Y(the_geom) WHEN ST_IsEmpty(ST_PointOnSurface(the_geom)) THEN null ELSE ST_Y(ST_PointOnSurface(the_geom)) END as latitude_0, ST_AsGeoJSON(the_geom,5) as geojson FROM (%%sql%%) as subq ",
  _MAX_SQL_GET_LENGTH: 1000,
  events: cdb.core.View.extendEvents({
    'click .export:not(.disabled)': 'export'
  }),

  /**
   * Allowed formats on the exporter
   * @type {Array}
   */
  formats: [
    {format: 'csv', fetcher: 'fetchCSV', geomRequired: false},
    {format: 'shp', fetcher: 'fetch', geomRequired: true},
    {format: 'kml', fetcher: 'fetch', geomRequired: true},
    {format: 'svg', fetcher: 'fetchSVG', geomRequired: true},
    {format: 'geojson', fetcher: 'fetch', geomRequired: true}
  ],

  dataGeoreferenced: null,

  initialize: function() {
    _.extend(this.options, {
      title: "Select your file type",
      description: '',
      template_name: 'common/views/dialog_base',
      clean_on_hide: true,
      ok_button_classes: "button grey",
      ok_title: "",
      modal_type: "",
      width: 510,
      modal_class: 'export_table_dialog',
      include_footer: false,
      table_id: this.model.id
    });
    this.constructor.__super__.initialize.apply(this);
    _.bindAll(this, 'export');
    this.baseUrl = this.options.config.sql_api_protocol + '://' + this.options.config.sql_api_domain
      + ':'
      + this.options.config.sql_api_port
      + '/api/v1/sql';
    this.checkGeoreference();
  },

  checkGeoreference: function() {
    var self = this;
    self.dataGeoreferenced = null;

    this.model.fetchGeoreferenceQueryStatus().done(function(res) {
      self.dataGeoreferenced = res;
      self.refresh();
    }).fail(function(res) {
      // handle error
    })

  },

  /**
   * search a format based on its name in the format array
   * @param  {string} format Format name
   * @return {Object}
   */
  getFormat: function(format) {
    for(var n in this.formats) {
      if(this.formats[n].format === format) {
        return this.formats[n]
      }
    }
  },

  /**
   * Answer to button event and lauchn the export method associated to that format
   * @param  {Event} ev
   */
  export: function(ev) {
    this.killEvent(ev);
    var $button = $(ev.currentTarget);
    var formatName = $button.data('format');
    var format = this.getFormat(formatName);
    this[format.fetcher](formatName);
  },

  /**
   * Create a dictionary with the options shared between all the methods
   * @return {Object}
   */
  getBaseOptions: function() {
    var options = {};

    options.filename = this.model.get('name');

    if (this.options.user_data) {
      options.api_key = this.options.user_data.api_key;
    }

    return options;
  },

  /**
   * Returns the base sql to retrieve the data
   * @return {string}
   */
  getPlainSql: function() {
    if(this.options.sql) {
      sql = this.options.sql;
    } else {
      if(this.model.sqlView) {
        sql = this.model.sqlView.getSQL();
      } else {
        sql = "select * from " + this.model.get('name')
      }
    }
    return sql;
  },

  /**
   * Returns a specific sql filtered by the_geom, used on CSV exports
   * @return {string}
   */
  getGeomFilteredSql: function() {
    var sql = this.getPlainSql();
    var currentSchema = this.model.get('schema');
    // if we have "the_geom" in our current schema, we apply a custom sql
    if(this.dataGeoreferenced) {
      return this._CSV_FILTER.replace(/%%sql%%/g, sql);
    } // if not, we apply regular sql
    return sql;

  },

  /**
   * Populates the hidden form with the format related values and submit them to get the file
   * @param  {Object} options Base options
   * @param  {String} sql Sql of the document to be retrieved
   */
  _fetch: function(options, sql) {
    var self = this;
    this.$('.error').html('').hide();
    this.$('.format').val(options.format);
    this.$('.q').val(sql);
    this.$('.filename').val(options.filename);
    this.$('.api_key').val(options.api_key);
    this.$('.generating').fadeIn();
    this.$('.geospatial').fadeOut();
    // check if the sql is big or not, and send the request as a verb or other. This is a HACK.
    if(sql.length < this._MAX_SQL_GET_LENGTH) {
      var location = this.$('form').attr('action') + '?' + this.$('form').serialize()
      this._fetchGET(location);
    } else {
      // I can't find a way of making the iframe trigger load event when its get a form posted,
      // so we need to leave like it was until
      this.submit();
    }

    this.$('.db').attr('disabled', 'disabled');
    this.$('.skipfields').attr('disabled', 'disabled');
    if(this.options.autoClose) {
      this.hide();
      this.trigger('generating', this.$('.generating').html());
    }

  },

  showError: function(error) {
    this.$('.generating').hide()
    this.$('.error').html("
" + error + "
").fadeIn();
  },

  _fetchGET: function(url) {

      function getError(content) {
        // sql api returns a json when it fails
        // but if the browser is running some plugin that
        // formats it, the window content is the html
        // so search for the word "error"
        var error = null;
        try {
          var json = JSON.parse(content);
          error = json.error[0];
        } catch(e) {
          if (content && content.indexOf('error') !== -1) {
            error = "an error ocurred";
          }
        }
        return error;
      }

      var self = this;
      var checkInterval;

      var w = window.open(url);//, "export"), "height=0;width=0");
      w.onload = function() {
        clearInterval(checkInterval);
        var error = getError(w.document.body.textContent);
        if(error) {
          self.showError(error);
        } else {
          self.hide();
        }
        w.close();
      };
      window.focus();
      checkInterval = setInterval(function check() {
        if (w.closed) {
          self.hide();
          clearInterval(checkInterval);
        }
      }, 100)
  },

  /**
   * Submits the form. This method is separated to ease the testing
   */
  submit: function() {
    this.$('form').submit();
  },

  /**
   * Base fetch, for the formats that don't require special threatment
   * @param  {String} formatName
   */
  fetch: function(formatName) {
    var options = this.getBaseOptions();
    options.format = formatName;
    var sql = this.getPlainSql();
    this._fetch(options, sql);
  },
  /**
   * Gets the options needed for csv format and fetch the document
   * @param  {String} formatName
   */
  fetchCSV: function() {
    var options = this.getBaseOptions();
    options.format = 'csv';
    var sql = this.getGeomFilteredSql();
    this.$('.skipfields').removeAttr('disabled');
    this._fetch(options, sql);
  },
  /**
   * Gets the options needed for svg format and fetch the document
   * @param  {String} formatName
   */
  fetchSVG: function(){
    this.$('.db').removeAttr('disabled');
    this.fetch('svg');
  },
  /**
   * Returns the html populated with current data
   * @return {String}
   */
  render_content: function() {
    return this.getTemplate('common/views/export_table_dialog')({
      formats:this.formats,
      url: this.baseUrl,
      isGeoreferenced: this.dataGeoreferenced
    });
  },

  refresh: function() {
    this.$('.content').html(this.render_content());
  }

});
<commit_msg>fix for safari CDB-521<commit_after>
/**
 * shows a dialog to get the table exported
 * new ExportTableDialog({
 *  table: table_model
 * })
 *
 */
cdb.admin.ExportTableDialog = cdb.admin.BaseDialog.extend({

  // _CSV_FILTER: "SELECT *, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_X(the_geom) ELSE ST_X(ST_PointOnSurface(the_geom)) END as longitude, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_Y(the_geom) ELSE ST_Y(ST_PointOnSurface(the_geom)) END as latitude, ST_AsGeoJSON(the_geom,5) as geojson FROM (%%sql%%) as subq",
  _CSV_FILTER: "SELECT *, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_X(the_geom) WHEN ST_IsEmpty(ST_PointOnSurface(the_geom)) THEN null ELSE ST_X(ST_PointOnSurface(the_geom)) END as longitude_0, CASE WHEN GeometryType(the_geom)='POINT' THEN ST_Y(the_geom) WHEN ST_IsEmpty(ST_PointOnSurface(the_geom)) THEN null ELSE ST_Y(ST_PointOnSurface(the_geom)) END as latitude_0, ST_AsGeoJSON(the_geom,5) as geojson FROM (%%sql%%) as subq ",
  _MAX_SQL_GET_LENGTH: 1000,
  events: cdb.core.View.extendEvents({
    'click .export:not(.disabled)': 'export'
  }),

  /**
   * Allowed formats on the exporter
   * @type {Array}
   */
  formats: [
    {format: 'csv', fetcher: 'fetchCSV', geomRequired: false},
    {format: 'shp', fetcher: 'fetch', geomRequired: true},
    {format: 'kml', fetcher: 'fetch', geomRequired: true},
    {format: 'svg', fetcher: 'fetchSVG', geomRequired: true},
    {format: 'geojson', fetcher: 'fetch', geomRequired: true}
  ],

  dataGeoreferenced: null,

  initialize: function() {
    _.extend(this.options, {
      title: "Select your file type",
      description: '',
      template_name: 'common/views/dialog_base',
      clean_on_hide: true,
      ok_button_classes: "button grey",
      ok_title: "",
      modal_type: "",
      width: 510,
      modal_class: 'export_table_dialog',
      include_footer: false,
      table_id: this.model.id
    });
    this.constructor.__super__.initialize.apply(this);
    _.bindAll(this, 'export');
    this.baseUrl = this.options.config.sql_api_protocol + '://' + this.options.config.sql_api_domain
      + ':'
      + this.options.config.sql_api_port
      + '/api/v1/sql';
    this.checkGeoreference();
  },

  checkGeoreference: function() {
    var self = this;
    self.dataGeoreferenced = null;

    this.model.fetchGeoreferenceQueryStatus().done(function(res) {
      self.dataGeoreferenced = res;
      self.refresh();
    }).fail(function(res) {
      // handle error
    })

  },

  /**
   * search a format based on its name in the format array
   * @param  {string} format Format name
   * @return {Object}
   */
  getFormat: function(format) {
    for(var n in this.formats) {
      if(this.formats[n].format === format) {
        return this.formats[n]
      }
    }
  },

  /**
   * Answer to button event and lauchn the export method associated to that format
   * @param  {Event} ev
   */
  export: function(ev) {
    this.killEvent(ev);
    var $button = $(ev.currentTarget);
    var formatName = $button.data('format');
    var format = this.getFormat(formatName);
    this[format.fetcher](formatName);
  },

  /**
   * Create a dictionary with the options shared between all the methods
   * @return {Object}
   */
  getBaseOptions: function() {
    var options = {};

    options.filename = this.model.get('name');

    if (this.options.user_data) {
      options.api_key = this.options.user_data.api_key;
    }

    return options;
  },

  /**
   * Returns the base sql to retrieve the data
   * @return {string}
   */
  getPlainSql: function() {
    if(this.options.sql) {
      sql = this.options.sql;
    } else {
      if(this.model.sqlView) {
        sql = this.model.sqlView.getSQL();
      } else {
        sql = "select * from " + this.model.get('name')
      }
    }
    return sql;
  },

  /**
   * Returns a specific sql filtered by the_geom, used on CSV exports
   * @return {string}
   */
  getGeomFilteredSql: function() {
    var sql = this.getPlainSql();
    var currentSchema = this.model.get('schema');
    // if we have "the_geom" in our current schema, we apply a custom sql
    if(this.dataGeoreferenced) {
      return this._CSV_FILTER.replace(/%%sql%%/g, sql);
    } // if not, we apply regular sql
    return sql;

  },

  /**
   * Populates the hidden form with the format related values and submit them to get the file
   * @param  {Object} options Base options
   * @param  {String} sql Sql of the document to be retrieved
   */
  _fetch: function(options, sql) {
    var self = this;
    this.$('.error').html('').hide();
    this.$('.format').val(options.format);
    this.$('.q').val(sql);
    this.$('.filename').val(options.filename);
    this.$('.api_key').val(options.api_key);
    this.$('.generating').fadeIn();
    this.$('.geospatial').fadeOut();
    // check if the sql is big or not, and send the request as a verb or other. This is a HACK.
    if(sql.length < this._MAX_SQL_GET_LENGTH) {
      var location = this.$('form').attr('action') + '?' + this.$('form').serialize()
      this._fetchGET(location);
    } else {
      // I can't find a way of making the iframe trigger load event when its get a form posted,
      // so we need to leave like it was until
      this.submit();
    }

    this.$('.db').attr('disabled', 'disabled');
    this.$('.skipfields').attr('disabled', 'disabled');
    if(this.options.autoClose) {
      this.hide();
      this.trigger('generating', this.$('.generating').html());
    }

  },

  showError: function(error) {
    this.$('.generating').hide()
    this.$('.error').html("
" + error + "
").fadeIn();
  },

  _fetchGET: function(url) {

      function getError(content) {
        // sql api returns a json when it fails
        // but if the browser is running some plugin that
        // formats it, the window content is the html
        // so search for the word "error"
        var error = null;
        try {
          var json = JSON.parse(content);
          error = json.error[0];
        } catch(e) {
          if (content && content.indexOf('error') !== -1) {
            error = "an error ocurred";
          }
        }
        return error;
      }

      var self = this;
      var checkInterval;

      var w = window.open(url);//, "export"), "height=0;width=0");
      w.onload = function() {
        clearInterval(checkInterval);
        var error = getError(w.document.body.textContent);
        if(error) {
          self.showError(error);
        } else {
          self.hide();
        }
        w.close();
      };
      window.focus();
      checkInterval = setInterval(function check() {
        // safari needs to check the body because it never
        // calls onload
        if (w.closed || (w.document && w.document.body.textContent.length === 0)) {
          self.hide();
          clearInterval(checkInterval);
        }
      }, 100)
  },

  /**
   * Submits the form. This method is separated to ease the testing
   */
  submit: function() {
    this.$('form').submit();
  },

  /**
   * Base fetch, for the formats that don't require special threatment
   * @param  {String} formatName
   */
  fetch: function(formatName) {
    var options = this.getBaseOptions();
    options.format = formatName;
    var sql = this.getPlainSql();
    this._fetch(options, sql);
  },
  /**
   * Gets the options needed for csv format and fetch the document
   * @param  {String} formatName
   */
  fetchCSV: function() {
    var options = this.getBaseOptions();
    options.format = 'csv';
    var sql = this.getGeomFilteredSql();
    this.$('.skipfields').removeAttr('disabled');
    this._fetch(options, sql);
  },
  /**
   * Gets the options needed for svg format and fetch the document
   * @param  {String} formatName
   */
  fetchSVG: function(){
    this.$('.db').removeAttr('disabled');
    this.fetch('svg');
  },
  /**
   * Returns the html populated with current data
   * @return {String}
   */
  render_content: function() {
    return this.getTemplate('common/views/export_table_dialog')({
      formats:this.formats,
      url: this.baseUrl,
      isGeoreferenced: this.dataGeoreferenced
    });
  },

  refresh: function() {
    this.$('.content').html(this.render_content());
  }

});
<|endoftext|>


<commit_before>/**
 * List object
 */

 /**
  * @public
  * @constructor
  */
 function List() {
     try {
         var self_ = this;
         self_.id_ = 'sosimplist-list' + (new Date().getTime());
         self_.view_ = null;
         self_.title_ = '';
         self_.mapOfItem_ = {};
         self_.checkedVisible_ = false;
         self_.dropdownButtonVisible_ = false;

         //add default item
         self_.addItem();
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
 }

 /**
 * @public
 */
List.prototype.buildView = function() {
    try {
        var self_ = this;
        if (self_.view_ === null) {
             self_.view_ = document.createElement('div');
             self_.view_.id = self_.id_;
             self_.view_.className = 'sosimplist-list';
             self_.view_.draggable = true;

             var inputTitle = document.createElement('input');
             inputTitle.id = 'sosimplist-title' + self_.id_;
             inputTitle.className = 'sosimplist-title';
             inputTitle.type = 'text';
             inputTitle.placeholder = 'Title';
             inputTitle.addEventListener(
                 'keyup',
                 function() { self_.title_ = this.value;},
                 false
             );
             if (self_.title_ !== '') {
                inputTitle.value = self_.title_;
             }else {}
             self_.view_.appendChild(inputTitle);

             self_.itemContainer_ = document.createElement('div');
             self_.itemContainer_.id = 'sosimplist-container-item';
             self_.itemContainer_.addEventListener(
                 'dragstart',
                 function(event) {
                    if(event.target.classList.contains('sosimplist-item')){
                        var parentToDrag = event.target.closest('.sosimplist-item');
                        parentToDrag.style.zIndex = 1;
                        parentToDrag.style.boxShadow = '3px 3px 3px grey';
                        event.dataTransfer.setData('elementId', parentToDrag.id);
                        event.dataTransfer.setData('source', 'item');

                        var mask = document.createElement('div');
                        mask.id = 'mask';
                        mask.style.backgroundColor = 'red'; //To check if opacity is working
                        mask.style.opacity = 0; // Should be not visible with opacity = 0
                        mask.style.width = parentToDrag.clientWidth;
                        mask.style.height = parentToDrag.clientHeight;
                        mask.style.cursor = 'move';
                        document.body.appendChild(mask);
                        event.dataTransfer.setDragImage(mask, 0, 0);
                    }else{}
                 },
                 false
             );
             self_.itemContainer_.addEventListener(
                 'dragenter',
                 function(event) {
                    event.preventDefault();

                    if(event.dataTransfer.getData('source') === 'item'){
                        var elementDragged = document.getElementById(event.dataTransfer.getData('elementId'));
                        if(elementDragged){
                            var parentTarget = event.target.closest('.sosimplist-item');
                            var isContainInThisList = parentTarget.parentNode.contains(elementDragged);
                            if(isContainInThisList){
                                elementDragged.nextSibling === parentTarget ?
                                elementDragged.parentNode.insertBefore(elementDragged, parentTarget.nextSibling) :
                                elementDragged.parentNode.insertBefore(elementDragged, parentTarget);
                            }else{}
                        }else{}
                    }else{}
                 },
                 false
             );
             self_.itemContainer_.addEventListener(
                'drop',
                function(event) {
                    if(event.dataTransfer.getData('source') === 'item'){
                        var elementDragged = document.getElementById(event.dataTransfer.getData('elementId'));
                        if(elementDragged){
                            document.body.removeChild(document.getElementById('mask'));
                            elementDragged.style.boxShadow = '';
                            elementDragged.style.zIndex = '0';
                        }else{}
                    }else{}
                },
                false
             );
             self_.itemContainer_.addEventListener(
                'dragover',
                function(event) {
                    event.preventDefault();
                },
                false
             );
             self_.view_.appendChild(self_.itemContainer_);

             var buttonAddItem = document.createElement('input');
             buttonAddItem.className = 'sosimplist-button';
             buttonAddItem.type = 'button';
             buttonAddItem.value = 'Add item';
             buttonAddItem.addEventListener(
                 'click',
                 function() { self_.addItem(); },
                 false
             );
             self_.view_.appendChild(buttonAddItem);

             var dropdownList = document.createElement('div');
             dropdownList.className = 'sosimplist-list-dropdown-checked';
             dropdownList.value = 'click';

             dropdownList.addEventListener(
                 'click',
                 function() {
                    self_.checkedVisible_ = !self_.checkedVisible_;
                    self_.setVisibility_();
                 },
                 false
             );
             var pin = document.createElement('div');
             pin.className = 'sosimplist-list-pin';
             dropdownList.appendChild(pin);
             var pinLabel = document.createElement('label');
             pinLabel.className = 'sosimplist-list-pin-label';
             pinLabel.innerHTML = 'Selected items';
             dropdownList.appendChild(pinLabel);
             self_.view_.appendChild(dropdownList);

             self_.itemContainerChecked_ = document.createElement('div');
             self_.itemContainerChecked_.className = 'sosimplist-container-item-checked';
             self_.view_.appendChild(self_.itemContainerChecked_);

             //Fill view view items
             for (var itemId in self_.mapOfItem_) {
                if (self_.mapOfItem_[itemId].isChecked()) {
                    self_.itemContainerChecked_.appendChild(self_.mapOfItem_[itemId].getView());
                }
                else {
                    self_.itemContainer_.appendChild(self_.mapOfItem_[itemId].getView());
                }
             }

             //Customize view depending on private members
             self_.setVisibility_();
         }
         else {
            console.log('List ID = ' + self_.id_ + ', View already builded !');
         }
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* @return {string} return content serialize in a base64 string
*/
List.prototype.serialize = function() {
    try {
        var self_ = this;
        var content = {
            id_: self_.id_,
            title_: self_.title_,
            arrayOfItem_: [] // parse element array to save item order
        };
        for (var i = 0; i < self_.itemContainer_.children.length; i++) {
            content.arrayOfItem_.push(self_.mapOfItem_[self_.itemContainer_.children[i].id].serialize());
        }
        for (var i = 0; i < self_.itemContainerChecked_.children.length; i++) {
            content.arrayOfItem_.push(self_.mapOfItem_[self_.itemContainerChecked_.children[i].id].serialize());
        }
        return btoa(JSON.stringify(content));
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
 * @public
 * @param {string} str content serialized in a base64 string to decode
 */
List.prototype.unserialize = function(str) {
    try {
        var self_ = this;
        var content = JSON.parse(atob(str));
        self_.id_ = content.id_;
        self_.title_ = content.title_;
        self_.mapOfItem_ = {};
        for (var i = 0; i < content.arrayOfItem_.length; i++) {
            var myItem = new ItemSimple(self_);
            myItem.unserialize(content.arrayOfItem_[i]);
            myItem.buildView();
            self_.mapOfItem_[myItem.getId()] = myItem;
        }
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* @return {string} return list id
*/
List.prototype.getListId = function() {
    return this.id_;
};

/**
* @public
* Add item to this list
*/
List.prototype.addItem = function() {
    try {
        var self_ = this;
        var myItem = new ItemSimple(self_);
        myItem.buildView();
        self_.mapOfItem_[myItem.getId()] = myItem;
        if (self_.view_ !== null) {
            self_.itemContainer_.appendChild(myItem.getView());
        }else {}
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* Remove given item from this list
* @param {object} item
*/
List.prototype.removeItem = function(item) {
    try {
        var self_ = this;
        self_.mapOfItem_[item.getId()] = undefined;
        if(self_.itemContainer_.contains(document.getElementById(item.getId()))){
            self_.itemContainer_.removeChild(document.getElementById(item.getId()));
        }
        else{
            self_.itemContainerChecked_.removeChild(document.getElementById(item.getId()));
        }
        self_.setVisibility_();
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* Move given item to the right list container (checked container or default container)
* @param {object} item
*/
List.prototype.moveItem = function(item) {
    try {
        var self_ = this;
        if (item.isChecked()) {
            self_.itemContainerChecked_.appendChild(document.getElementById(item.getId()));
        }
        else {
            self_.itemContainer_.appendChild(document.getElementById(item.getId()));
        }
        self_.setVisibility_();
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

 /**
  * @public
  * @return {string} return view as Element object to be placed in the view
  */
 List.prototype.getView = function() {
     return this.view_;
 };

 /**
  * @private
  */
 List.prototype.setVisibility_ = function() {
    try {
        var self_ = this;
         //hide/show list of item checked
        if (self_.checkedVisible_) {
            self_.view_.getElementsByClassName('sosimplist-container-item-checked')[0].style.display = '';
        }
        else {
            self_.view_.getElementsByClassName('sosimplist-container-item-checked')[0].style.display = 'none';
        }

        self_.dropdownButtonVisible_ = self_.itemContainerChecked_.hasChildNodes();
        if (self_.dropdownButtonVisible_) {
            self_.view_.getElementsByClassName('sosimplist-list-dropdown-checked')[0].style.display = '';
        }
        else {
            self_.view_.getElementsByClassName('sosimplist-list-dropdown-checked')[0].style.display = 'none';
        }
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
 };

<commit_msg>Description: Fix drag data transfer issue http://stackoverflow.com/questions/34451024/why-my-javascript-does-not-work-properly-on-a-github-page?noredirect=1#comment56653223_34451024<commit_after>/**
 * List object
 */

 /**
  * @public
  * @constructor
  */
 function List() {
     try {
         var self_ = this;
         self_.id_ = 'sosimplist-list' + (new Date().getTime());
         self_.view_ = null;
         self_.title_ = '';
         self_.mapOfItem_ = {};
         self_.checkedVisible_ = false;
         self_.dropdownButtonVisible_ = false;
         self_.dragData = null;

         //add default item
         self_.addItem();
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
 }

 /**
 * @public
 */
List.prototype.buildView = function() {
    try {
        var self_ = this;
        if (self_.view_ === null) {
             self_.view_ = document.createElement('div');
             self_.view_.id = self_.id_;
             self_.view_.className = 'sosimplist-list';
             self_.view_.draggable = true;

             var inputTitle = document.createElement('input');
             inputTitle.id = 'sosimplist-title' + self_.id_;
             inputTitle.className = 'sosimplist-title';
             inputTitle.type = 'text';
             inputTitle.placeholder = 'Title';
             inputTitle.addEventListener(
                 'keyup',
                 function() { self_.title_ = this.value;},
                 false
             );
             if (self_.title_ !== '') {
                inputTitle.value = self_.title_;
             }else {}
             self_.view_.appendChild(inputTitle);

             self_.itemContainer_ = document.createElement('div');
             self_.itemContainer_.id = 'sosimplist-container-item';
             self_.itemContainer_.addEventListener(
                 'dragstart',
                 function(event) {
                    if(event.target.classList.contains('sosimplist-item')){
                        var parentToDrag = event.target.closest('.sosimplist-item');
                        parentToDrag.style.zIndex = 1;
                        parentToDrag.style.boxShadow = '3px 3px 3px grey';
                        self_.dragData = {
                            elementId:parentToDrag.id,
                            source:'item'
                        };

                        var mask = document.createElement('div');
                        mask.id = 'mask';
                        mask.style.backgroundColor = 'red'; //To check if opacity is working
                        mask.style.opacity = 0; // Should be not visible with opacity = 0
                        mask.style.width = parentToDrag.clientWidth;
                        mask.style.height = parentToDrag.clientHeight;
                        mask.style.cursor = 'move';
                        document.body.appendChild(mask);
                        event.dataTransfer.setDragImage(mask, 0, 0);
                    }else{}
                 },
                 false
             );
             self_.itemContainer_.addEventListener(
                 'dragenter',
                 function(event) {
                    event.preventDefault();

                    if(self_.dragData && self_.dragData.source === 'item'){
                        var elementDragged = document.getElementById(self_.dragData.elementId);
                        if(elementDragged){
                            var parentTarget = event.target.closest('.sosimplist-item');
                            var isContainInThisList = parentTarget.parentNode.contains(elementDragged);
                            if(isContainInThisList){
                                elementDragged.nextSibling === parentTarget ?
                                elementDragged.parentNode.insertBefore(elementDragged, parentTarget.nextSibling) :
                                elementDragged.parentNode.insertBefore(elementDragged, parentTarget);
                            }else{}
                        }else{}
                    }else{}
                 },
                 false
             );
             self_.itemContainer_.addEventListener(
                'drop',
                function(event) {
                    if(self_.dragData && self_.dragData.source === 'item'){
                        var elementDragged = document.getElementById(self_.dragData.elementId);
                        if(elementDragged){
                            document.body.removeChild(document.getElementById('mask'));
                            elementDragged.style.boxShadow = '';
                            elementDragged.style.zIndex = '0';
                            self_.dragData = null;
                        }else{}
                    }else{}
                },
                false
             );
             self_.itemContainer_.addEventListener(
                'dragover',
                function(event) {
                    event.preventDefault();
                },
                false
             );
             self_.view_.appendChild(self_.itemContainer_);

             var buttonAddItem = document.createElement('input');
             buttonAddItem.className = 'sosimplist-button';
             buttonAddItem.type = 'button';
             buttonAddItem.value = 'Add item';
             buttonAddItem.addEventListener(
                 'click',
                 function() { self_.addItem(); },
                 false
             );
             self_.view_.appendChild(buttonAddItem);

             var dropdownList = document.createElement('div');
             dropdownList.className = 'sosimplist-list-dropdown-checked';
             dropdownList.value = 'click';

             dropdownList.addEventListener(
                 'click',
                 function() {
                    self_.checkedVisible_ = !self_.checkedVisible_;
                    self_.setVisibility_();
                 },
                 false
             );
             var pin = document.createElement('div');
             pin.className = 'sosimplist-list-pin';
             dropdownList.appendChild(pin);
             var pinLabel = document.createElement('label');
             pinLabel.className = 'sosimplist-list-pin-label';
             pinLabel.innerHTML = 'Selected items';
             dropdownList.appendChild(pinLabel);
             self_.view_.appendChild(dropdownList);

             self_.itemContainerChecked_ = document.createElement('div');
             self_.itemContainerChecked_.className = 'sosimplist-container-item-checked';
             self_.view_.appendChild(self_.itemContainerChecked_);

             //Fill view view items
             for (var itemId in self_.mapOfItem_) {
                if (self_.mapOfItem_[itemId].isChecked()) {
                    self_.itemContainerChecked_.appendChild(self_.mapOfItem_[itemId].getView());
                }
                else {
                    self_.itemContainer_.appendChild(self_.mapOfItem_[itemId].getView());
                }
             }

             //Customize view depending on private members
             self_.setVisibility_();
         }
         else {
            console.log('List ID = ' + self_.id_ + ', View already builded !');
         }
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* @return {string} return content serialize in a base64 string
*/
List.prototype.serialize = function() {
    try {
        var self_ = this;
        var content = {
            id_: self_.id_,
            title_: self_.title_,
            arrayOfItem_: [] // parse element array to save item order
        };
        for (var i = 0; i < self_.itemContainer_.children.length; i++) {
            content.arrayOfItem_.push(self_.mapOfItem_[self_.itemContainer_.children[i].id].serialize());
        }
        for (var i = 0; i < self_.itemContainerChecked_.children.length; i++) {
            content.arrayOfItem_.push(self_.mapOfItem_[self_.itemContainerChecked_.children[i].id].serialize());
        }
        return btoa(JSON.stringify(content));
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
 * @public
 * @param {string} str content serialized in a base64 string to decode
 */
List.prototype.unserialize = function(str) {
    try {
        var self_ = this;
        var content = JSON.parse(atob(str));
        self_.id_ = content.id_;
        self_.title_ = content.title_;
        self_.mapOfItem_ = {};
        for (var i = 0; i < content.arrayOfItem_.length; i++) {
            var myItem = new ItemSimple(self_);
            myItem.unserialize(content.arrayOfItem_[i]);
            myItem.buildView();
            self_.mapOfItem_[myItem.getId()] = myItem;
        }
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* @return {string} return list id
*/
List.prototype.getListId = function() {
    return this.id_;
};

/**
* @public
* Add item to this list
*/
List.prototype.addItem = function() {
    try {
        var self_ = this;
        var myItem = new ItemSimple(self_);
        myItem.buildView();
        self_.mapOfItem_[myItem.getId()] = myItem;
        if (self_.view_ !== null) {
            self_.itemContainer_.appendChild(myItem.getView());
        }else {}
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* Remove given item from this list
* @param {object} item
*/
List.prototype.removeItem = function(item) {
    try {
        var self_ = this;
        self_.mapOfItem_[item.getId()] = undefined;
        if(self_.itemContainer_.contains(document.getElementById(item.getId()))){
            self_.itemContainer_.removeChild(document.getElementById(item.getId()));
        }
        else{
            self_.itemContainerChecked_.removeChild(document.getElementById(item.getId()));
        }
        self_.setVisibility_();
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

/**
* @public
* Move given item to the right list container (checked container or default container)
* @param {object} item
*/
List.prototype.moveItem = function(item) {
    try {
        var self_ = this;
        if (item.isChecked()) {
            self_.itemContainerChecked_.appendChild(document.getElementById(item.getId()));
        }
        else {
            self_.itemContainer_.appendChild(document.getElementById(item.getId()));
        }
        self_.setVisibility_();
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
};

 /**
  * @public
  * @return {string} return view as Element object to be placed in the view
  */
 List.prototype.getView = function() {
     return this.view_;
 };

 /**
  * @private
  */
 List.prototype.setVisibility_ = function() {
    try {
        var self_ = this;
         //hide/show list of item checked
        if (self_.checkedVisible_) {
            self_.view_.getElementsByClassName('sosimplist-container-item-checked')[0].style.display = '';
        }
        else {
            self_.view_.getElementsByClassName('sosimplist-container-item-checked')[0].style.display = 'none';
        }

        self_.dropdownButtonVisible_ = self_.itemContainerChecked_.hasChildNodes();
        if (self_.dropdownButtonVisible_) {
            self_.view_.getElementsByClassName('sosimplist-list-dropdown-checked')[0].style.display = '';
        }
        else {
            self_.view_.getElementsByClassName('sosimplist-list-dropdown-checked')[0].style.display = 'none';
        }
    }
    catch (e) {
        console.error(e.name + ': ' + e.message);
    }
 };

<|endoftext|>


<commit_before>var cdb = require('cartodb.js');
var handleAHref = require('new_common/view_helpers/handle_a_href_on_click');
var pluralizeString = require('new_common/view_helpers/pluralize_string');
var LockItemsDialog = require('new_dashboard/dialogs/lock_items_view');
var DeleteItemsDialog = require('new_dashboard/dialogs/delete_items/view');

/**
 *  Dashboard filters.
 *
 *  - 'Order by' (time,likes,etc) collection.
 *  - 'Filter by' collection.
 *  - 'Search' any pattern within collection.
 *
 */
module.exports = cdb.core.View.extend({

  _TOOLTIPS: ['js-likes', 'js-mapviews', 'js-updated_at', 'js-size'],

  events: {
    'submit .js-search-form':   '_submitSearch',
    'click .js-search-form':    'killEvent',
    'click .js-search-link':    '_onSearchClick',
    'click .js-clean-search':   '_onCleanSearchClick',
    'click .js-deselect_all':   '_unselectAll',
    'click .js-select_all':     '_selectAll',
    'click .js-order-link':     '_changeOrder',
    'click .js-delete':         '_openDeleteItemsDialog',
    'click .js-lock':           '_openLockItemsDialog',
    'click .js-link':           handleAHref
  },

  initialize: function() {
    this.router = this.options.router;
    this.user = this.options.user;
    this.localStorage = this.options.localStorage;
    this.template = cdb.templates.getTemplate('new_dashboard/views/filters');

    this._preRender();
    this._initBinds();
  },

  // It is necessary to add two static elements because
  // they can't be removed/replaced using render method
  // each time a change (in a model or a collection) happens.
  // This is due to the behaviour of the CSS animations.
  _preRender: function() {
    var $uInner = $('<div>').addClass('u-inner');
    var $filtersInner = $('<div>').addClass('Filters-inner');
    this.$el.append($uInner.append($filtersInner));
  },

  render: function(m, c) {
    this.clearSubViews();

    var selectedItemsCount = this._selectedItems().length;
    // If a change is made from content type we have to know
    // preventing show wrong data about total items
    var changedContentType = c && c.changes && c.changes.content_type;

    this.$('.Filters-inner').html(
      this.template(
        _.extend({
            order:                 this.localStorage.get('dashboard.order'),
            isInsideOrg:           this.user.isInsideOrg(),
            prefix:                cdb.config.prefixUrl(),
            selectedItemsCount:    selectedItemsCount,
            maxLayersByMap:        this.user.get('max_layers'),
            totalShared:           changedContentType ? 0 : this.collection.total_shared,
            totalLiked:            changedContentType ? 0 : this.collection.total_likes,
            totalItems:            changedContentType ? 0 : this.collection.total_entries,
            pageItems:             this.collection.size(),
            routerModel:           this.router.model,
            pluralizedContentType: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', changedContentType ? 0 : this.collection.total_entries),
            pluralizedContentTypeSelected: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', selectedItemsCount)
          },
          this.router.model.attributes
        )
      )
    );

    this._initViews();
    this._checkScroll();
    this._animate();

    return this;
  },

  _initBinds: function() {
    this.router.model.bind('change', this.render, this);
    this.collection.bind('add remove change reset', this.render, this);
    cdb.god.bind('closeDialogs', this._animate, this);
    _.bindAll(this, '_onWindowScroll');

    this.add_related_model(this.collection);
    this.add_related_model(this.router.model);
    this.add_related_model(cdb.god);
  },

  _checkScroll: function() {
    var content_type = this.router.model.get('content_type');
    var shared = this.router.model.get('shared');
    var locked = this.router.model.get('locked');
    var liked = this.router.model.get('liked');
    var total_entries = this.collection.total_entries;

    // Bind scroll
    if (total_entries === 0 && content_type === "maps" && !shared && !locked && !liked) {
      // If there is no maps, onboarding should appear
      // and filters block should be after that section
      this.$el.addClass('is-relative');
      this._unbindScroll();
    } else {
      this.$el.removeClass('is-relative');
      this._bindScroll();
    }
  },

  _bindScroll: function() {
    this._unbindScroll();
    $(window).bind('scroll', this._onWindowScroll);
  },

  _unbindScroll: function() {
    $(window).unbind('scroll', this._onWindowScroll);
  },

  _initViews: function() {
    // Tipsys?
    var self = this;
    _.each(this._TOOLTIPS, function(el,i){
      self.addView(
        new cdb.common.TipsyTooltip({
          el: self.$('.' + el),
          title: function(e) {
            return $(this).attr('data-title')
          }
        })
      )
    });
  },

  _selectedItems: function() {
    return this.collection.where({ selected: true });
  },

  _animate: function() {
    // Show filters or selected items actions
    var selectedItemsCount = this._selectedItems().length;
    this.$('.Filters-inner')[ selectedItemsCount > 0 ? 'addClass' : 'removeClass' ]('items--selected');

    // Check if any search is applied
    var search = this.router.model.get('q') || this.router.model.get('tag');
    this.$('.Filters-inner')[ search ? 'addClass' : 'removeClass' ]('search--enabled');
  },

  // Selection actions

  _selectAll: function(e) {
    if (e) e.preventDefault();
    this.collection.each(function(map) {
      if (!map.get('selected'))
        map.set('selected', true)
    })
  },

  _unselectAll: function(e) {
    if (e) e.preventDefault();
    this.collection.each(function(map) {
      if (map.get('selected'))
        map.set('selected', false)
    });
  },

  _openDeleteItemsDialog: function(e) {
    this.killEvent(e);
    (new DeleteItemsDialog({
      selectedItems: this._selectedItems(),
      router: this.router,
      user: this.user
    })).appendToBody();
  },

  _openLockItemsDialog: function(e) {
    this.killEvent(e);
    var lockDialog = new LockItemsDialog({
      items: this._selectedItems(),
      contentType: this.router.model.get('content_type')
    });
    lockDialog.bind('done', function() {
      this.collection.fetch();
    }, this);
    lockDialog.appendToBody();
  },

  _changeMapsPrivacy: function() {
    console.log("TODO: implement privacy modal dialog");
  },

  _onSearchClick: function(e) {
    if (e) this.killEvent(e);
    this.$('.Filters-inner').addClass('search--enabled');
    this.$('.js-search-input').focus();
  },

  // Creation actions

  _newMap: function() {
    console.log("TODO: implement new map modal dialog");
  },

  _newDataset: function() {
    console.log("TODO: implement new dataset modal dialog");
  },

  // Filter actions

  _onCleanSearchClick: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: ''
    });
  },

  _submitSearch: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: this.$('.js-search-input').val().trim()
    });
  },

  _navigateToUrl: function(opts) {
    this.router.navigate(this.router.model.url(opts), { trigger: true });
  },

  _changeOrder: function(e) {
    if (e) e.preventDefault();

    var $el = $(e.target).closest('.js-order-link');
    var order = 'updated_at';

    if ($el.hasClass('js-mapviews')) order = 'mapviews';
    if ($el.hasClass('js-likes')) order = 'likes';
    if ($el.hasClass('js-size')) order = 'size';

    // Order change?
    if (this.router.model.get('order') !== order) {
      this.localStorage.set({ 'dashboard.order': order });
      this.router.model.set('order', order);
    }
  },

  _onWindowScroll: function() {
    var offset = $(window).scrollTop();
    this.$el[ offset > 81 ? 'addClass' : 'removeClass' ]('is-fixed')
  },

  clean: function() {
    this._unbindScroll();
    cdb.core.View.prototype.clean.call(this);
  }

});
<commit_msg>Remove last prefixUrl usage (filters view)<commit_after>var cdb = require('cartodb.js');
var handleAHref = require('new_common/view_helpers/handle_a_href_on_click');
var pluralizeString = require('new_common/view_helpers/pluralize_string');
var LockItemsDialog = require('new_dashboard/dialogs/lock_items_view');
var DeleteItemsDialog = require('new_dashboard/dialogs/delete_items/view');

/**
 *  Dashboard filters.
 *
 *  - 'Order by' (time,likes,etc) collection.
 *  - 'Filter by' collection.
 *  - 'Search' any pattern within collection.
 *
 */
module.exports = cdb.core.View.extend({

  _TOOLTIPS: ['js-likes', 'js-mapviews', 'js-updated_at', 'js-size'],

  events: {
    'submit .js-search-form':   '_submitSearch',
    'click .js-search-form':    'killEvent',
    'click .js-search-link':    '_onSearchClick',
    'click .js-clean-search':   '_onCleanSearchClick',
    'click .js-deselect_all':   '_unselectAll',
    'click .js-select_all':     '_selectAll',
    'click .js-order-link':     '_changeOrder',
    'click .js-delete':         '_openDeleteItemsDialog',
    'click .js-lock':           '_openLockItemsDialog',
    'click .js-link':           handleAHref
  },

  initialize: function() {
    this.router = this.options.router;
    this.user = this.options.user;
    this.localStorage = this.options.localStorage;
    this.template = cdb.templates.getTemplate('new_dashboard/views/filters');

    this._preRender();
    this._initBinds();
  },

  // It is necessary to add two static elements because
  // they can't be removed/replaced using render method
  // each time a change (in a model or a collection) happens.
  // This is due to the behaviour of the CSS animations.
  _preRender: function() {
    var $uInner = $('<div>').addClass('u-inner');
    var $filtersInner = $('<div>').addClass('Filters-inner');
    this.$el.append($uInner.append($filtersInner));
  },

  render: function(m, c) {
    this.clearSubViews();

    var selectedItemsCount = this._selectedItems().length;
    // If a change is made from content type we have to know
    // preventing show wrong data about total items
    var changedContentType = c && c.changes && c.changes.content_type;

    this.$('.Filters-inner').html(
      this.template(
        _.extend({
            order:                 this.localStorage.get('dashboard.order'),
            isInsideOrg:           this.user.isInsideOrg(),
            selectedItemsCount:    selectedItemsCount,
            maxLayersByMap:        this.user.get('max_layers'),
            totalShared:           changedContentType ? 0 : this.collection.total_shared,
            totalLiked:            changedContentType ? 0 : this.collection.total_likes,
            totalItems:            changedContentType ? 0 : this.collection.total_entries,
            pageItems:             this.collection.size(),
            routerModel:           this.router.model,
            pluralizedContentType: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', changedContentType ? 0 : this.collection.total_entries),
            pluralizedContentTypeSelected: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', selectedItemsCount)
          },
          this.router.model.attributes
        )
      )
    );

    this._initViews();
    this._checkScroll();
    this._animate();

    return this;
  },

  _initBinds: function() {
    this.router.model.bind('change', this.render, this);
    this.collection.bind('add remove change reset', this.render, this);
    cdb.god.bind('closeDialogs', this._animate, this);
    _.bindAll(this, '_onWindowScroll');

    this.add_related_model(this.collection);
    this.add_related_model(this.router.model);
    this.add_related_model(cdb.god);
  },

  _checkScroll: function() {
    var content_type = this.router.model.get('content_type');
    var shared = this.router.model.get('shared');
    var locked = this.router.model.get('locked');
    var liked = this.router.model.get('liked');
    var total_entries = this.collection.total_entries;

    // Bind scroll
    if (total_entries === 0 && content_type === "maps" && !shared && !locked && !liked) {
      // If there is no maps, onboarding should appear
      // and filters block should be after that section
      this.$el.addClass('is-relative');
      this._unbindScroll();
    } else {
      this.$el.removeClass('is-relative');
      this._bindScroll();
    }
  },

  _bindScroll: function() {
    this._unbindScroll();
    $(window).bind('scroll', this._onWindowScroll);
  },

  _unbindScroll: function() {
    $(window).unbind('scroll', this._onWindowScroll);
  },

  _initViews: function() {
    // Tipsys?
    var self = this;
    _.each(this._TOOLTIPS, function(el,i){
      self.addView(
        new cdb.common.TipsyTooltip({
          el: self.$('.' + el),
          title: function(e) {
            return $(this).attr('data-title')
          }
        })
      )
    });
  },

  _selectedItems: function() {
    return this.collection.where({ selected: true });
  },

  _animate: function() {
    // Show filters or selected items actions
    var selectedItemsCount = this._selectedItems().length;
    this.$('.Filters-inner')[ selectedItemsCount > 0 ? 'addClass' : 'removeClass' ]('items--selected');

    // Check if any search is applied
    var search = this.router.model.get('q') || this.router.model.get('tag');
    this.$('.Filters-inner')[ search ? 'addClass' : 'removeClass' ]('search--enabled');
  },

  // Selection actions

  _selectAll: function(e) {
    if (e) e.preventDefault();
    this.collection.each(function(map) {
      if (!map.get('selected'))
        map.set('selected', true)
    })
  },

  _unselectAll: function(e) {
    if (e) e.preventDefault();
    this.collection.each(function(map) {
      if (map.get('selected'))
        map.set('selected', false)
    });
  },

  _openDeleteItemsDialog: function(e) {
    this.killEvent(e);
    (new DeleteItemsDialog({
      selectedItems: this._selectedItems(),
      router: this.router,
      user: this.user
    })).appendToBody();
  },

  _openLockItemsDialog: function(e) {
    this.killEvent(e);
    var lockDialog = new LockItemsDialog({
      items: this._selectedItems(),
      contentType: this.router.model.get('content_type')
    });
    lockDialog.bind('done', function() {
      this.collection.fetch();
    }, this);
    lockDialog.appendToBody();
  },

  _changeMapsPrivacy: function() {
    console.log("TODO: implement privacy modal dialog");
  },

  _onSearchClick: function(e) {
    if (e) this.killEvent(e);
    this.$('.Filters-inner').addClass('search--enabled');
    this.$('.js-search-input').focus();
  },

  // Creation actions

  _newMap: function() {
    console.log("TODO: implement new map modal dialog");
  },

  _newDataset: function() {
    console.log("TODO: implement new dataset modal dialog");
  },

  // Filter actions

  _onCleanSearchClick: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: ''
    });
  },

  _submitSearch: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: this.$('.js-search-input').val().trim()
    });
  },

  _navigateToUrl: function(opts) {
    this.router.navigate(this.router.model.url(opts), { trigger: true });
  },

  _changeOrder: function(e) {
    if (e) e.preventDefault();

    var $el = $(e.target).closest('.js-order-link');
    var order = 'updated_at';

    if ($el.hasClass('js-mapviews')) order = 'mapviews';
    if ($el.hasClass('js-likes')) order = 'likes';
    if ($el.hasClass('js-size')) order = 'size';

    // Order change?
    if (this.router.model.get('order') !== order) {
      this.localStorage.set({ 'dashboard.order': order });
      this.router.model.set('order', order);
    }
  },

  _onWindowScroll: function() {
    var offset = $(window).scrollTop();
    this.$el[ offset > 81 ? 'addClass' : 'removeClass' ]('is-fixed')
  },

  clean: function() {
    this._unbindScroll();
    cdb.core.View.prototype.clean.call(this);
  }

});
<|endoftext|>


<commit_before>import chalk from 'chalk';
import { get, includes, defaults } from 'lodash';
import {DEBUG} from './flags';

const formatMessage = (err, options = {}) => {
    const codes = defaults({
        ENOENT: 'No such file or directory: ' + err.path
    }, args.errorMessages);
    return codes[err.code] || err.message;
}

const errorHandler = (args = {}) => {
    const debug = includes(args.argv, `--${DEBUG}`);
    return (err) => {
        console.log(chalk.red('Error:'))
        console.log(chalk.red(formatMessage(err)));
        if (debug) {
            console.log('');
            console.log(err.stack);
            console.log('');
        }
        process.exit(1);
    }
}

export default errorHandler;
<commit_msg>Fix error in errorHandler<commit_after>import chalk from 'chalk';
import { get, includes, defaults } from 'lodash';
import {DEBUG} from './flags';

const formatMessage = (err, args = {}) => {
    const codes = defaults({
        ENOENT: 'No such file or directory: ' + err.path
    }, args.errorMessages);
    return codes[err.code] || err.message;
}

const errorHandler = (args = {}) => {
    const debug = includes(args.argv, `--${DEBUG}`);
    return (err) => {
        console.log(chalk.red('Error:'))
        console.log(chalk.red(formatMessage(err, args)));
        if (debug) {
            console.log('');
            console.log(err.stack);
            console.log('');
        }
        process.exit(1);
    }
}

export default errorHandler;
<|endoftext|>


<commit_before>'use strict'

const Writable = require('stream').Writable;
const parse = require('./parse');

/*
scope.get = (key, emitter, cb) => {

    let item = scope.cache.get(key);
    let newItem;
    if (!item) {
        newItem = true;
        item = new EventEmitter();
        scope.cache.set(key, item);
        item.on('error', console.error.bind(console));
    }

    if (item.ready) {
        process.nextTick(cb.bind(emitter, item));
    } else {
        item.once('ready', cb.bind(emitter));
    }

    return !!newItem;
};
*/

module.exports = (scope, node, args) => {

    let loader = new Writable({
        objectMode: true,
        write: (triple, enc, done) => {
            done(
                parse(
                    loader,
                    triple.subject || triple[0],
                    triple.predicate || triple[1],
                    triple.object || triple[2]
                )
            );
        }
    });

    loader.args = args;
    loader.scope = scope;
    loader.check = {};
    loader.on('error', console.error.bind(console));
    loader.on('finish', () => {
        console.log('LOADER FINISH:', scope.cache.keys());
        //node.link();
    });

    loader.on('done', (what) => {
        switch (what) {
            case 'read':
                loader.check.read = true;
            break;
        }
    });

    return loader;
};
<commit_msg>parse triple here<commit_after>'use strict'

const Writable = require('stream').Writable;
const Core = require('./core');

/*
function isBlankNode (string) {
    return string.indexOf('_:') === 0;
}

function buildObject (data, subject, predicate, object) {
    if (data[subject]) {
        if (data[subject][predicate]) {
            if (!(data[subject][predicate] instanceof Array)) {
                data[subject][predicate] = [data[subject][predicate]];
            }
            data[subject][predicate].push(object);
        } else {
            data[subject][predicate] = object;
        }
    } else {
        data[subject] = {};
        data[subject][predicate] = object;
    }
}
*/

module.exports = (scope, node, args) => {

    let loader = new Writable({
        objectMode: true,
        write: (triple, enc, done) => {

            if (!(triple instanceof Array)) {
                triple = [
                    triple.subject,
                    triple.predicate,
                    triple.object
                ];
            }

            let subject = triple[0];
            let predicate = triple[1];
            let object = triple[2];

            switch (predicate) {
                // load the module
                case 'http://schema.jillix.net/vocab/module':
                    Core.module(loader, subject, predicate, object);
                    return;

                // load the instance
                case 'http://schema.jillix.net/vocab/instance':
                    Core.instance(loader, subject, predicate, object);
                    return;

                // parse the event
                case 'http://schema.jillix.net/vocab/event':
                    Core.event(loader, subject, predicate, object);
                    return;

                // create sequence
                case 'http://schema.jillix.net/vocab/sequence':
                    Core.sequence(loader, subject, predicate, object);
                    return;

                // get data handler method reference
                case 'http://schema.jillix.net/vocab/dataHandler':
                    Core.dataHandler(loader, subject, predicate, object);
                    return;

                // get stream handler method reference
                case 'http://schema.jillix.net/vocab/streamHandler':
                    Core.streamHandler(loader, subject, predicate, object);
                    return;

                // emit flow event and add stream to sequence, if S is a BN
                case 'http://schema.jillix.net/vocab/EventEmit':
                    Core.eventHandler(loader, subject, predicate, object);
                    return;

                // build the object in the blank node
                default:
                    Core.object(loader, subject, predicate, object);
                    return;
            }

            done();
        }
    });

    loader.buffer = {};
    loader.ids = {};
    loader.index = {};

    loader.args = args;
    loader.scope = scope;

    loader.on('error', console.error.bind(console));
    loader.on('finish', () => {
        //console.log('Cach keys:', scope.cache.keys());
        //console.log(loader.ids);
        //console.log(loader.buffer);
        //node.link();
    });

    loader.on('done', (what) => {
        //console.log('DONE:', what);
    });

    return loader;
};
<|endoftext|>


<commit_before>
/**
 * dropdown to select the column type when user clicks
 * on the type
 */
cdb.admin.ColumntypeDropdown = cdb.admin.DropdownMenu.extend({

  className: 'dropdown border',
  dialogContainer: 'body',
  deactivateMatrix: {
      'number': ['date'],
      'boolean': ['date'],
      'date': ['boolean']
  },

  events: {
    'click .string': 'setString',
    'click .number': 'setNumber',
    'click .date': 'setDate',
    'click .boolean': 'setBool'
  },

  initialize: function() {
    _.bindAll(this, 'setColumnTypeChange', 'setString', 'setNumber', 'setDate', 'setBool');
    this.elder('initialize');
  },

  setTable: function(table, column) {
    var self = this;
    this.table = table;
    this.column = column;
    var type = this.table.getColumnType(column);
    this._disableColumn(type);
    var deactivate = this.deactivateMatrix[type] || [];
    _(deactivate).each(function(c) { self._disableColumn(c, { keep_disabled: true }); });
  },

  // Disable current selected column
  _disableColumn: function(type, options) {
    options = options || {};
    if(!options.keep_disabled) {
      this.$('li.disabled').removeClass('disabled');
    }
    this.$('li a.' + type).parent().addClass('disabled');
  },

  setColumnTypeChange: function(type) {
    var self = this;
    self.hide();

    if (this.table.isTypeChangeDestructive(this.column, type)) {

      this.change_confirmation = new cdb.admin.BaseDialog({
        title: "Confirm type change",
        description: "Unconvertible data will be lost. Are you sure?",
        template_name: 'old_common/views/confirm_dialog',
        clean_on_hide: true,
        enter_to_confirm: true,
        ok_button_classes: "right button grey",
        ok_title: "Yes, do it",
        cancel_button_classes: "underline margin15",
        cancel_title: "Cancel",
        modal_type: "confirmation",
        width: 500
      });

      this.change_confirmation.ok = function() {
        self.table.changeColumnType(self.column, type);
      }

      this.change_confirmation
        .appendToBody()
        .open();
    } else {
      this.table.changeColumnType(this.column, type);
    }
  },

  //TODO: move to the model
  _canCast: function(type, newType) {
    var deactivated = this.deactivateMatrix[type] || [];
    deactivated = deactivated.concat([type])
    return !_.contains(deactivated, newType);
  },

  setString: function(e) {
    this.killEvent(e);
    if (this._canCast(this.table.getColumnType(this.column), 'string'))
      this.setColumnTypeChange('string');
  },

  setNumber: function(e) {
    this.killEvent(e);
    if (this._canCast(this.table.getColumnType(this.column), 'number'))
      this.setColumnTypeChange('number');
  },

  setDate: function(e) {
    this.killEvent(e);
    if (this._canCast(this.table.getColumnType(this.column), 'date'))
      this.setColumnTypeChange('date');
  },

  setBool: function(e) {
    this.killEvent(e);
    if (this._canCast(this.table.getColumnType(this.column), 'boolean'))
      this.setColumnTypeChange('boolean');
  }

});
<commit_msg>Using new table method to check if a column cast is valid<commit_after>
/**
 * dropdown to select the column type when user clicks
 * on the type
 */
cdb.admin.ColumntypeDropdown = cdb.admin.DropdownMenu.extend({

  className: 'dropdown border',
  dialogContainer: 'body',

  events: {
    'click .string': 'setString',
    'click .number': 'setNumber',
    'click .date': 'setDate',
    'click .boolean': 'setBool'
  },

  initialize: function() {
    _.bindAll(this, 'setColumnTypeChange', 'setString', 'setNumber', 'setDate', 'setBool');
    this.elder('initialize');
  },

  setTable: function(table, column) {
    var self = this;
    this.table = table;
    this.column = column;
    var type = this.table.getColumnType(column);
    this._disableColumn(type);
    _(["string", "number", "boolean", "date"]).each(function(c) {
      if (!self.table.isTypeChangeAllowed(self.column,c)) {
        self._disableColumn(c, { keep_disabled: true });
      }
    });
  },

  // Disable current selected column
  _disableColumn: function(type, options) {
    options = options || {};
    if(!options.keep_disabled) {
      this.$('li.disabled').removeClass('disabled');
    }
    this.$('li a.' + type).parent().addClass('disabled');
  },

  setColumnTypeChange: function(type) {
    var self = this;
    self.hide();

    if (this.table.isTypeChangeDestructive(this.column, type)) {

      this.change_confirmation = new cdb.admin.BaseDialog({
        title: "Confirm type change",
        description: "Unconvertible data will be lost. Are you sure?",
        template_name: 'old_common/views/confirm_dialog',
        clean_on_hide: true,
        enter_to_confirm: true,
        ok_button_classes: "right button grey",
        ok_title: "Yes, do it",
        cancel_button_classes: "underline margin15",
        cancel_title: "Cancel",
        modal_type: "confirmation",
        width: 500
      });

      this.change_confirmation.ok = function() {
        self.table.changeColumnType(self.column, type);
      }

      this.change_confirmation
        .appendToBody()
        .open();
    } else {
      this.table.changeColumnType(this.column, type);
    }
  },

  setString: function(e) {
    this.killEvent(e);
    if (this.table.isTypeChangeAllowed(this.column, 'string'))
      this.setColumnTypeChange('string');
  },

  setNumber: function(e) {
    this.killEvent(e);
    if (this.table.isTypeChangeAllowed(this.column, 'number'))
      this.setColumnTypeChange('number');
  },

  setDate: function(e) {
    this.killEvent(e);
    if (this.table.isTypeChangeAllowed(this.column, 'date'))
      this.setColumnTypeChange('date');
  },

  setBool: function(e) {
    this.killEvent(e);
    if (this.table.isTypeChangeAllowed(this.column, 'boolean'))
      this.setColumnTypeChange('boolean');
  }

});
<|endoftext|>


<commit_before>const { lock } = require('proper-lockfile')

const staleSeconds = 30

// Obtains a lock on the path, and maintains it until the callback finishes
async function withLock (path, options = {}, callback = options) {
  // Obtain the lock
  const releaseLock = await lock(path, {
    retries: 10,
    update: 1000,
    stale: staleSeconds * 1000,
    realpath: !!options.mustExist,
    onCompromised: () => {
      throw new Error(`The file at ${path} was not updated within ${staleSeconds}s.`)
    }
  })

  // Try executing the callback, waiting for its returned promise to resolve
  try {
    return await callback()
  } finally {
    // Ensure the lock is always released
    releaseLock()
  }
}

module.exports = withLock
<commit_msg>Use local error instead of global for stale locks.<commit_after>const { lock } = require('proper-lockfile')

const staleSeconds = 30

// Obtains a lock on the path, and maintains it until the callback finishes
function withLock (path, options = {}, callback = options) {
  return new Promise(async (resolve, reject) => {
    let releaseLock, result
    try {
      // Obtain the lock
      releaseLock = await lock(path, {
        retries: 10,
        update: 1000,
        stale: staleSeconds * 1000,
        realpath: !!options.mustExist,
        onCompromised: () =>
          reject(new Error(`The file at ${path} was not updated within ${staleSeconds}s.`))
      })
      // Hold on to the lock until the callback's returned promise resolves
      result = await callback()
    } catch (error) {
      reject(error)
    // Ensure the lock is always released
    } finally {
      await releaseLock()
    }
    resolve(result)
  })
}

module.exports = withLock
<|endoftext|>


<commit_before>(function(window, undefined) {

    var URIParser = {
        query: function(query_string, decode) {
            if (typeof decode === 'undefined') decode = true;

            query_string = query_string.replace(/^\?/, '');
            var parts = query_string.split('&');
            var query = {};

            for (var i = 0, l = parts.length; i < l; i++) {
                var part = parts[i];
                var key;
                var val = '';

                if (part.match('=')) {
                    var part_parts = part.split('=');
                    key = part_parts[0];
                    val = part_parts[1];
                } else {
                    key = part;
                }

                if (val !== '') {
                    val = decodeURIComponent(val);
                }

                query[key] = val;
            }

            return query;
        },

        _extend: function() {
            if (arguments.length > 2) {
                var obj = Array.prototype.shift.call(arguments);

                while (arguments.length > 0) {
                    obj = arguments.callee(obj, Array.prototype.shift.call(arguments));
                }

                return obj;
            }

            var one = arguments[0];
            var two = arguments[1];

            for (var key in two) {
                if (typeof two.hasOwnProperty === 'undefined' || two.hasOwnProperty(key)) {
                    var current = two[key];

                    if (typeof current === 'object' && typeof one[key] === 'object') {
                        one[key] = extend(one[key], current);
                    } else {
                        one[key] = current;
                    }
                }
            }

            return one;
        }
    }

    if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
        module.exports = exports = URIParser;
    } else if (typeof define !== 'undefined') {
        define(function(require, exports, module) {
            module.exports = exports = URIParser;
        });
    } else {
        window.URIParser = URIParser;

        var location = window.location;
        var defaults = {
            auto: {
                query: false
            },

            keys: {
                query: 'query'
            }
        };

        if (!location.query_opts) {
            location.query_opts = {};
        };

        var opts = URIParser._extend({}, defaults, location.query_opts);
        delete location['query_opts'];

        if (opts.auto || (typeof opts.auto === 'object' && opts.auto.query)) {
            location[opts.keys.query] = URIParser.query(location.search);
        }
    }
})(window);<commit_msg>Add comments<commit_after>(function(window, undefined) {

    var URIParser = {

        /**
         * Parse URI query strings
         * @param  {string} query_string The query string to parse
         * @param  {bool}   decode       Should values be URI decoded?
         * @return {object}              The decoded query string
         */
        query: function(query_string, decode) {
            // Default decode to true
            if (typeof decode === 'undefined') decode = true;

            // Replace the starting ?, if it is there
            query_string = query_string.replace(/^\?/, '');

            // Split the query string into key value parts
            var parts = query_string.split('&');

            // Iniate the return value
            var query = {};

            // Loop through each other the parts, splitting it into keys and values
            for (var i = 0, l = parts.length; i < l; i++) {
                var part = parts[i];
                var key;
                var val = '';

                if (part.match('=')) {
                    // If it is in the format key=val

                    // Split into the key and value by the = symbol
                    var part_parts = part.split('=');

                    // Assign key and value
                    key = part_parts[0];
                    val = part_parts[1];
                } else {
                    // If there is no value, just set the key to the full part
                    key = part;
                }

                // If we actually have a value, URI decode it
                if (val !== '') {
                    val = decodeURIComponent(val);
                }

                // Assign to the return value
                query[key] = val;
            }

            return query;
        },

        _extend: function() {
            // If we have more than two objects to merge
            if (arguments.length > 2) {
                // Get the first object.
                // Everything will merge into this.
                var obj = Array.prototype.shift.call(arguments);

                // While we have any more arguments, call extend with the initial obj and the next argument
                while (arguments.length > 0) {
                    obj = arguments.callee(obj, Array.prototype.shift.call(arguments));
                }

                return obj;
            }

            // Pull our objects out of the arguments array.
            var one = arguments[0];
            var two = arguments[1];

            // Loop through the second object to merge it with the first
            for (var key in two) {
                // If this key actually belongs to the second argument
                if (typeof two.hasOwnProperty === 'undefined' || two.hasOwnProperty(key)) {
                    var current = two[key];

                    if (typeof current === 'object' && typeof one[key] === 'object') {
                        // Deep copy
                        one[key] = extend(one[key], current);
                    } else {
                        one[key] = current;
                    }
                }
            }

            return one;
        }
    }

    if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
        // CommonJS, Node, PhantomJS, etc modules
        module.exports = exports = URIParser;
    } else if (typeof define !== 'undefined') {
        // RequireJS Module
        define(function(require, exports, module) {
            module.exports = exports = URIParser;
        });
    } else {
        // Being included directly in the browser
        window.URIParser = URIParser;

        var location = window.location;
        var defaults = {
            auto: {
                query: false
            },

            keys: {
                query: 'query'
            }
        };

        if (!location.query_opts) {
            location.query_opts = {};
        };

        var opts = URIParser._extend({}, defaults, location.query_opts);
        delete location['query_opts'];

        if (opts.auto || (typeof opts.auto === 'object' && opts.auto.query)) {
            location[opts.keys.query] = URIParser.query(location.search);
        }
    }
})(window);<|endoftext|>


<commit_before>'use strict';

const config = require('../config');
const CronJob = require('cron').CronJob;
const Fetcher = require('./fetcher');
const Promise = require("bluebird");

class Main {
  constructor(ds) {
    let self = this;
    this.banks = {};
    this.banksId = {};
    this.banksIdMsg = '';
    this.prices = {};
    this.bestPrices = {};
    this.needReloadPrice = false;

    this.ds = ds;
    this.fetcher = new Fetcher(self);

    this.initBankNames();
    this.initLastPrices();

    // Fetch currecy price every 60 minutes
    new CronJob('0 0 * * * *', function() {
      self.fetchAllCurrency();
    }, null, true);
  }

  initBankNames() {
    let self = this;
    self.ds.bank.findAndCountAll()
      .then((res) => {
        if (res.count > 0) {
          self.initBankMap(res.rows);
          console.log('Loaded', res.rows.length, 'banks.');
        } else {
          return self.insertBankNames();
        }
      });
  }

  insertBankNames() {
    let self = this;
    return this.fetcher.fetchBankNames()
      .then((names) => {
        if (names.length) {
          return self.ds.bank.bulkCreate(names);
        }
      })
      .then((banks) => {
        if (banks.length) {
          self.initBankMap(banks);
          console.log('Inserted', banks.length, 'banks.');
        } else {
          console.warn('Cannot fetch bank names.');
        }
      });
  }

  initBankMap(banks) {
    let self = this;
    banks.forEach(bank => {
      self.banks[bank.id] = bank.name;
      self.banksId[bank.name] = bank.id;
    });

    let msg = 'Available bank id: \n';
    let ids = Object.keys(self.banks);
    ids.forEach(id => {
      msg += id + ': ' + self.banks[id] + ', ';
    });
    msg = msg.slice(0, -2);
    self.banksIdMsg = msg;
  }

  initLastPrices() {
    let self = this;
    self.bestPrices = {};
    config.availCurrencies.forEach(c => {
      self.prices[c] = [];
      self.bestPrices[c] = {stt: -999, snt: -999, btt: 999, bnt: 999};
    })
    let sql = 'SELECT DISTINCT ON (code, bank) ' +
      'code, bank, stt, snt, btt, bnt ' +
      'FROM histories ORDER BY code, bank, ts DESC';
    return self.ds.sequelize.query(sql, {
      type: self.ds.sequelize.QueryTypes.SELECT,
      raw: true
    })
    .then((prices) => {
      let count = 0;
      prices.forEach(price => {
        self.prices[price.code][price.bank] = price;
        self.initBestPrices(price);
        delete price.code;
        delete price.bank;
        count++;
      });
      console.log('Loaded', count, 'records.');
    });
  }

  initBestPrices(price) {
    let self = this;
    let code = this.bestPrices[price.code];
    ['stt', 'snt'].forEach((f) => {
      if (price[f] && price[f] > code[f]) {
        code[f] = price[f];
        code[f + 'b'] = +price.bank;
      }
    });
    ['btt', 'bnt'].forEach(f => {
      if (price[f] && price[f] < code[f]) {
        code[f] = price[f];
        code[f + 'b'] = +price.bank;
      }
    });
  }

  fetchAllCurrency() {
    let self = this;
    self.needReloadPrice = false;
    return Promise.map(config.availCurrencies, (code) => {
      return self.insertCurrency(code).delay(1000);
    }, {concurrency: 1})
    .then(function() {
      console.log('Finished fetch all currencies');
      if (self.needReloadPrice) {
        self.initLastPrices();
      }
    });
  }

  insertCurrency(code) {
    let self = this;
    return self.fetcher.fetchCurrency(code)
      .then((prices) => {
        if (!prices.length) {
          return false;
        }
        let count = 0;
        prices.forEach(price => {
          let bank = self.prices[price.code][price.bank];
          if (!bank || (bank.stt !== price.stt || bank.btt !== price.btt ||
            bank.snt !== price.snt || bank.bnt !== price.bnt)) {
            self.ds.history.create(price);
            count++;
          }
        });
        if (count > 0) {
          self.needReloadPrice = true;
          console.log('Inserted', count, code, 'records.');
        }
      });
  }

  inlineAnswer(args) {
    let self = this;
    args = args.toUpperCase().split(' ');
    let code = args[0];
    let bank = self._parseIntArg(args[1]);
    console.log('inlineAnswer', code, bank);
    if (config.availCurrencies.indexOf(code) > 0) {
      let a1 = self._listCurrency_sub(code, bank);
      let t1 = a1.split('\n')[0];
      let a2 = self._listCurrency_sub(code, bank, 1);
      let t2 = a2.split('\n')[0];
      return  [{title: t1, message_text: a1}, {title: t2, message_text: a2}];
    }
  }

  _parseIntArg(arg) {
    return isNaN(parseInt(arg)) ? false : +arg;
  }

  listCurrency(args) {
    let self = this;
    args = args.toUpperCase().split(' ');
    let code = args[0];
    let bank = self._parseIntArg(args[1]);
    let reverse = self._parseIntArg(args[2]);
    console.log('listCurrency', code, bank, reverse);
    if (config.availCurrencies.indexOf(code) > 0) {
      return self._listCurrency_sub(code, bank, reverse);
    } else if (code === 'ALL') {
      let res = '';
      config.availCurrencies.forEach(code => {
        res += self._listCurrency_sub(code, bank, reverse);
      });
      return res;
    }
  }

  _listCurrency_sub(code, bank, reverse) {
    let self = this;
    let fields = ['stt', 'snt', 'btt', 'bnt'];
    let names = ['電匯銀行買入', '現鈔銀行買入', '電匯銀行賣出', '現鈔銀行賣出'];
    let price = self.bestPrices[code];
    if (bank) {
      price = self.prices[code][bank];
      if (!price) {
        return '';
      }
    }
    let res =  (reverse ? 'HKD/' + code : code + '/HKD') + '\n';
    let i = 0;
    fields.forEach(f => {
      let pbank = bank || price[f + 'b'];
      if (pbank && price[f]) {
        let val = reverse ? (1 / price[f]).toFixed(6) : price[f];
        res += names[i] + ': ' + val + ' ' + self.banks[pbank] + '\n';
      }
      i++;
    });
    return res;
  }
}

module.exports = Main;
<commit_msg>Update fetch interval<commit_after>'use strict';

const config = require('../config');
const CronJob = require('cron').CronJob;
const Fetcher = require('./fetcher');
const Promise = require("bluebird");

class Main {
  constructor(ds) {
    let self = this;
    this.banks = {};
    this.banksId = {};
    this.banksIdMsg = '';
    this.prices = {};
    this.bestPrices = {};
    this.needReloadPrice = false;

    this.ds = ds;
    this.fetcher = new Fetcher(self);

    this.initBankNames();
    this.initLastPrices();

    // Fetch currecy price every 10 minutes during trading
    new CronJob('0 */10 8-23 * * *', function() {
      self.fetchAllCurrency();
    }, null, true);
    // Fetch currecy price every 30 minutes after trading
    new CronJob('0 */30 0-7 * * *', function() {
      self.fetchAllCurrency();
    }, null, true);
  }

  initBankNames() {
    let self = this;
    self.ds.bank.findAndCountAll()
      .then((res) => {
        if (res.count > 0) {
          self.initBankMap(res.rows);
          console.log('Loaded', res.rows.length, 'banks.');
        } else {
          return self.insertBankNames();
        }
      });
  }

  insertBankNames() {
    let self = this;
    return this.fetcher.fetchBankNames()
      .then((names) => {
        if (names.length) {
          return self.ds.bank.bulkCreate(names);
        }
      })
      .then((banks) => {
        if (banks.length) {
          self.initBankMap(banks);
          console.log('Inserted', banks.length, 'banks.');
        } else {
          console.warn('Cannot fetch bank names.');
        }
      });
  }

  initBankMap(banks) {
    let self = this;
    banks.forEach(bank => {
      self.banks[bank.id] = bank.name;
      self.banksId[bank.name] = bank.id;
    });

    let msg = 'Available bank id: \n';
    let ids = Object.keys(self.banks);
    ids.forEach(id => {
      msg += id + ': ' + self.banks[id] + ', ';
    });
    msg = msg.slice(0, -2);
    self.banksIdMsg = msg;
  }

  initLastPrices() {
    let self = this;
    self.bestPrices = {};
    config.availCurrencies.forEach(c => {
      self.prices[c] = [];
      self.bestPrices[c] = {stt: -999, snt: -999, btt: 999, bnt: 999};
    })
    let sql = 'SELECT DISTINCT ON (code, bank) ' +
      'code, bank, stt, snt, btt, bnt ' +
      'FROM histories ORDER BY code, bank, ts DESC';
    return self.ds.sequelize.query(sql, {
      type: self.ds.sequelize.QueryTypes.SELECT,
      raw: true
    })
    .then((prices) => {
      let count = 0;
      prices.forEach(price => {
        self.prices[price.code][price.bank] = price;
        self.initBestPrices(price);
        delete price.code;
        delete price.bank;
        count++;
      });
      console.log('Loaded', count, 'records.');
    });
  }

  initBestPrices(price) {
    let self = this;
    let code = this.bestPrices[price.code];
    ['stt', 'snt'].forEach((f) => {
      if (price[f] && price[f] > code[f]) {
        code[f] = price[f];
        code[f + 'b'] = +price.bank;
      }
    });
    ['btt', 'bnt'].forEach(f => {
      if (price[f] && price[f] < code[f]) {
        code[f] = price[f];
        code[f + 'b'] = +price.bank;
      }
    });
  }

  fetchAllCurrency() {
    let self = this;
    self.needReloadPrice = false;
    return Promise.map(config.availCurrencies, (code) => {
      return self.insertCurrency(code).delay(1000);
    }, {concurrency: 1})
    .then(function() {
      console.log('Finished fetch all currencies');
      if (self.needReloadPrice) {
        self.initLastPrices();
      }
    });
  }

  insertCurrency(code) {
    let self = this;
    return self.fetcher.fetchCurrency(code)
      .then((prices) => {
        if (!prices.length) {
          return false;
        }
        let count = 0;
        prices.forEach(price => {
          let bank = self.prices[price.code][price.bank];
          if (!bank || (bank.stt !== price.stt || bank.btt !== price.btt ||
            bank.snt !== price.snt || bank.bnt !== price.bnt)) {
            self.ds.history.create(price);
            count++;
          }
        });
        if (count > 0) {
          self.needReloadPrice = true;
          console.log('Inserted', count, code, 'records.');
        }
      });
  }

  inlineAnswer(args) {
    let self = this;
    args = args.toUpperCase().split(' ');
    let code = args[0];
    let bank = self._parseIntArg(args[1]);
    console.log('inlineAnswer', code, bank);
    if (config.availCurrencies.indexOf(code) > 0) {
      let a1 = self._listCurrency_sub(code, bank);
      let t1 = a1.split('\n')[0];
      let a2 = self._listCurrency_sub(code, bank, 1);
      let t2 = a2.split('\n')[0];
      return  [{title: t1, message_text: a1}, {title: t2, message_text: a2}];
    }
  }

  _parseIntArg(arg) {
    return isNaN(parseInt(arg)) ? false : +arg;
  }

  listCurrency(args) {
    let self = this;
    args = args.toUpperCase().split(' ');
    let code = args[0];
    let bank = self._parseIntArg(args[1]);
    let reverse = self._parseIntArg(args[2]);
    console.log('listCurrency', code, bank, reverse);
    if (config.availCurrencies.indexOf(code) > 0) {
      return self._listCurrency_sub(code, bank, reverse);
    } else if (code === 'ALL') {
      let res = '';
      config.availCurrencies.forEach(code => {
        res += self._listCurrency_sub(code, bank, reverse);
      });
      return res;
    }
  }

  _listCurrency_sub(code, bank, reverse) {
    let self = this;
    let fields = ['stt', 'snt', 'btt', 'bnt'];
    let names = ['電匯銀行買入', '現鈔銀行買入', '電匯銀行賣出', '現鈔銀行賣出'];
    let price = self.bestPrices[code];
    if (bank) {
      price = self.prices[code][bank];
      if (!price) {
        return '';
      }
    }
    let res =  (reverse ? 'HKD/' + code : code + '/HKD') + '\n';
    let i = 0;
    fields.forEach(f => {
      let pbank = bank || price[f + 'b'];
      if (pbank && price[f]) {
        let val = reverse ? (1 / price[f]).toFixed(6) : price[f];
        res += names[i] + ': ' + val + ' ' + self.banks[pbank] + '\n';
      }
      i++;
    });
    return res;
  }
}

module.exports = Main;
<|endoftext|>