<commit_before>var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var PanelWithOptionsView = require('../../components/view-options/panel-with-options-view');
var StyleContentView = require('./style-content-view');
var StyleCartoCSSView = require('./style-cartocss-view');
var ScrollView = require('../../components/scroll/scroll-view');
var TabPaneView = require('../../components/tab-pane/tab-pane-view');
var TabPaneCollection = require('../../components/tab-pane/tab-pane-collection');
var Toggler = require('../../components/toggler/toggler-view');
var UndoButtons = require('../../components/undo-redo/undo-redo-view');
var ParserCSS = require('../../helpers/parser-css');
var Infobox = require('../../components/infobox/infobox-factory');
var InfoboxModel = require('../../components/infobox/infobox-model');
var InfoboxCollection = require('../../components/infobox/infobox-collection');
var Notifier = require('../../components/notifier/notifier');
var CartoCSSNotifications = require('../../cartocss-notifications');
var MetricsTracker = require('../../components/metrics/metrics-tracker');

module.exports = CoreView.extend({

  initialize: function (opts) {
    if (!opts.layerDefinitionsCollection) throw new Error('layersDefinitionCollection is required');
    if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
    if (!opts.userActions) throw new Error('userActions is required');
    if (!opts.querySchemaModel) throw new Error('querySchemaModel is required');
    if (!opts.modals) throw new Error('modals is required');
    if (!opts.editorModel) throw new Error('editorModel is required');
    if (!opts.configModel) throw new Error('configModel is required');

    this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
    this._layerDefinitionModel = opts.layerDefinitionModel;
    this._userActions = opts.userActions;
    this._styleModel = this._layerDefinitionModel.styleModel;
    this._configModel = opts.configModel;
    this._modals = opts.modals;
    this._querySchemaModel = opts.querySchemaModel;
    this._editorModel = opts.editorModel;
    this._cartocssModel = this._layerDefinitionModel.cartocssModel;
    this._codemirrorModel = new Backbone.Model({
      content: this._layerDefinitionModel.get('cartocss')
    });

    // Set edition attribute in case custom cartocss is applied
    this._editorModel.set({
      edition: !!this._layerDefinitionModel.get('cartocss_custom'),
      disabled: false
    });

    this._infoboxModel = new InfoboxModel({
      state: this._isLayerHidden() ? 'layer-hidden' : ''
    });

    this._overlayModel = new Backbone.Model({
      visible: this._isLayerHidden()
    });

    this.listenTo(this._editorModel, 'change:edition', this._onChangeEdition);
    this.add_related_model(this._editorModel);

    this._appendMapsAPIError();
    this.listenTo(this._layerDefinitionModel, 'change:error', this._appendMapsAPIError);

    CartoCSSNotifications.track(this);

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

  _appendMapsAPIError: function () {
    var error = this._layerDefinitionModel.get('error');
    if (error) {
      if (error.type === 'turbo-carto') {
        var newErrors = _.clone(this._codemirrorModel.get('errors')) || [];
        newErrors.push({ line: error.line, message: error.message });
        this._codemirrorModel.set('errors', newErrors);
      }
    }
  },

  render: function () {
    this.clearSubViews();
    this.$el.empty();
    this._initViews();
    return this;
  },

  _initBinds: function () {
    this._querySchemaModel.on('change:query_errors', this._updateEditor, this);
    this.add_related_model(this._querySchemaModel);

    this._styleModel.bind('change', function () {
      this._codemirrorModel.set('content', this._layerDefinitionModel.get('cartocss'));
    }, this);
    this.add_related_model(this._styleModel);

    this._cartocssModel.bind('undo redo', function () {
      this._codemirrorModel.set('content', this._cartocssModel.get('content'));
    }, this);
    this.add_related_model(this._cartocssModel);
  },

  _updateEditor: function (model) {
    var errors = this._querySchemaModel.get('query_errors');
    var hasErrors = errors && errors.length > 0;
    this._editorModel.set('disabled', hasErrors);
  },

  _saveCartoCSS: function () {
    var content = this._codemirrorModel.get('content');
    var parser = new ParserCSS(content);
    var errors = parser.errors();

    if (!content) {
      return false;
    }

    this._codemirrorModel.set('errors', parser.parseError(errors));

    if (errors.length === 0) {
      this._cartocssModel.set('content', content);
      this._layerDefinitionModel.set({
        cartocss_custom: true,
        cartocss: content
      });

      this._userActions.saveLayer(this._layerDefinitionModel);

      CartoCSSNotifications.showSuccessNotification();

      MetricsTracker.track('Applied Cartocss', this._layerDefinitionModel.attributes);
    } else {
      CartoCSSNotifications.showErrorNotification(parser.parseError(errors));
    }
  },

  _confirmApplyStyles: function () {
    this._infoboxModel.set({state: ''});
    this._overlayModel.set({visible: false});

    this._editorModel.set({
      edition: true
    });
  },

  _cancelApplyStyles: function () {
    this._styleModel.applyLastState();
    this._infoboxModel.set({state: ''});
    this._overlayModel.set({visible: false});
    this.render();
  },

  _cancelTorqueAggregation: function () {
    this._styleModel.applyLastState();
    this._infoboxModel.set({state: ''});
    this._overlayModel.set({visible: false});
    this.render();
  },

  _applyTorqueAggregation: function () {
    if (this._layerDefinitionsCollection.isThereAnyTorqueLayer()) {
      var torqueLayer = this._layerDefinitionsCollection.findWhere({ type: 'torque' });
      torqueLayer.styleModel.setDefaultPropertiesByType('simple', 'point');
    }

    this._infoboxModel.set({state: 'unfreeze'});
  },

  _onChangeEdition: function () {
    this._infoboxState();

    var edition = this._editorModel.get('edition');
    var index = edition ? 1 : 0;
    this._collectionPane.at(index).set({ selected: true });
  },

  _freezeTorgeAggregation: function (styleType, currentGeometryType) {
    this._infoboxModel.set({state: 'torque-exists'});
    this._overlayModel.set({visible: true});

    // Apply the previously selected style
    this._infoboxModel.once('change:state', function (mdl, state) {
      if (state === 'unfreeze') {
        this._moveTorqueLayerToTop(function () {
          this._overlayModel.set({visible: false});
          this._styleModel.setDefaultPropertiesByType(styleType, currentGeometryType);
        }.bind(this));
        this._infoboxModel.set({state: ''});
      }
    }, this);
  },

  _moveTorqueLayerToTop: function (callback) {
    var notification = Notifier.addNotification({
      status: 'loading',
      info: _t('editor.layers.moveTorqueLayer.loading'),
      closable: true
    });

    this._layerDefinitionsCollection.once('layerMoved', function () {
      callback && callback();
      notification.set({
        status: 'success',
        info: _t('editor.layers.moveTorqueLayer.success'),
        delay: Notifier.DEFAULT_DELAY
      });
    }, this);

    this._userActions.moveLayer({
      from: this._layerDefinitionModel.get('order'),
      to: this._layerDefinitionsCollection.getTopDataLayerIndex()
    });
  },

  _configPanes: function () {
    var self = this;
    var tabPaneTabs = [{
      selected: !this._layerDefinitionModel.get('cartocss_custom'),
      createContentView: function () {
        return new ScrollView({
          createContentView: function () {
            return new StyleContentView({
              className: 'Editor-content',
              userActions: self._userActions,
              layerDefinitionsCollection: self._layerDefinitionsCollection,
              layerDefinitionModel: self._layerDefinitionModel,
              styleModel: self._styleModel,
              modals: self._modals,
              configModel: self._configModel,
              querySchemaModel: self._querySchemaModel,
              editorModel: self._editorModel,
              overlayModel: self._overlayModel,
              freezeTorgeAggregation: self._freezeTorgeAggregation.bind(self)
            });
          }
        });
      },
      createActionView: function () {
        return new UndoButtons({
          trackModel: self._styleModel,
          editorModel: self._editorModel,
          applyButton: false
        });
      }
    }, {
      selected: this._layerDefinitionModel.get('cartocss_custom'),
      createContentView: function () {
        return new StyleCartoCSSView({
          layerDefinitionModel: self._layerDefinitionModel,
          styleModel: self._styleModel,
          editorModel: self._editorModel,
          codemirrorModel: self._codemirrorModel,
          onApplyEvent: self._saveCartoCSS.bind(self)
        });
      },
      createActionView: function () {
        return new UndoButtons({
          trackModel: self._cartocssModel,
          editorModel: self._editorModel,
          applyButton: true,
          onApplyClick: self._saveCartoCSS.bind(self)
        });
      }
    }];

    this._collectionPane = new TabPaneCollection(tabPaneTabs);
  },

  _isLayerHidden: function () {
    return this._layerDefinitionModel.get('visible') === false;
  },

  _infoboxState: function () {
    var edition = this._editorModel.get('edition');
    var cartocss_custom = this._layerDefinitionModel.get('cartocss_custom');

    if (!edition && cartocss_custom) {
      this._infoboxModel.set({state: 'confirm'});
      this._overlayModel.set({visible: true});
    } else if (!edition && this._isLayerHidden()) {
      this._infoboxModel.set({state: 'layer-hidden'});
      this._overlayModel.set({visible: true});
    } else {
      this._infoboxModel.set({state: ''});
      this._overlayModel.set({visible: false});
    }
  },

  _showHiddenLayer: function () {
    this._layerDefinitionModel.toggleVisible();
    this._userActions.saveLayer(this._layerDefinitionModel);

    this._infoboxState();
  },

  _initViews: function () {
    var self = this;

    var infoboxSstates = [
      {
        state: 'confirm',
        createContentView: function () {
          return Infobox.createConfirmAndCancel({
            type: 'alert',
            title: _t('editor.style.messages.cartocss-applied.title'),
            body: _t('editor.style.messages.cartocss-applied.body'),
            cancelLabel: _t('editor.style.messages.cartocss-applied.cancel'),
            confirmLabel: _t('editor.style.messages.cartocss-applied.accept')
          });
        },
        mainAction: self._cancelApplyStyles.bind(self),
        secondAction: self._confirmApplyStyles.bind(self)
      }, {
        state: 'layer-hidden',
        createContentView: function () {
          return Infobox.createConfirm({
            type: 'alert',
            title: _t('editor.style.messages.layer-hidden.title'),
            body: _t('editor.style.messages.layer-hidden.body'),
            confirmLabel: _t('editor.style.messages.layer-hidden.show'),
            confirmType: 'button'
          });
        },
        mainAction: self._showHiddenLayer.bind(self)
      }, {
        state: 'torque-exists',
        createContentView: function () {
          return Infobox.createConfirmAndCancel({
            type: 'alert',
            title: _t('editor.style.messages.torque-exists.title'),
            body: _t('editor.style.messages.torque-exists.body'),
            cancelLabel: _t('editor.style.messages.torque-exists.cancel'),
            confirmLabel: _t('editor.style.messages.torque-exists.accept')
          });
        },
        mainAction: self._cancelTorqueAggregation.bind(self),
        secondAction: self._applyTorqueAggregation.bind(self)
      }
    ];

    var infoboxCollection = new InfoboxCollection(infoboxSstates);

    var panelWithOptionsView = new PanelWithOptionsView({
      className: 'Editor-content',
      editorModel: self._editorModel,
      infoboxModel: self._infoboxModel,
      infoboxCollection: infoboxCollection,
      createContentView: function () {
        return new TabPaneView({
          collection: self._collectionPane
        });
      },
      createControlView: function () {
        return new Toggler({
          editorModel: self._editorModel,
          labels: [_t('editor.style.style-toggle.values'), _t('editor.style.style-toggle.cartocss')],
          isDisableable: true
        });
      },
      createActionView: function () {
        return new TabPaneView({
          collection: self._collectionPane,
          createContentKey: 'createActionView'
        });
      }
    });

    this.$el.append(panelWithOptionsView.render().el);
    this.addView(panelWithOptionsView);
  }

});
<commit_msg>Applied Cartocss event properties changed<commit_after>var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var PanelWithOptionsView = require('../../components/view-options/panel-with-options-view');
var StyleContentView = require('./style-content-view');
var StyleCartoCSSView = require('./style-cartocss-view');
var ScrollView = require('../../components/scroll/scroll-view');
var TabPaneView = require('../../components/tab-pane/tab-pane-view');
var TabPaneCollection = require('../../components/tab-pane/tab-pane-collection');
var Toggler = require('../../components/toggler/toggler-view');
var UndoButtons = require('../../components/undo-redo/undo-redo-view');
var ParserCSS = require('../../helpers/parser-css');
var Infobox = require('../../components/infobox/infobox-factory');
var InfoboxModel = require('../../components/infobox/infobox-model');
var InfoboxCollection = require('../../components/infobox/infobox-collection');
var Notifier = require('../../components/notifier/notifier');
var CartoCSSNotifications = require('../../cartocss-notifications');
var MetricsTracker = require('../../components/metrics/metrics-tracker');

module.exports = CoreView.extend({

  initialize: function (opts) {
    if (!opts.layerDefinitionsCollection) throw new Error('layersDefinitionCollection is required');
    if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
    if (!opts.userActions) throw new Error('userActions is required');
    if (!opts.querySchemaModel) throw new Error('querySchemaModel is required');
    if (!opts.modals) throw new Error('modals is required');
    if (!opts.editorModel) throw new Error('editorModel is required');
    if (!opts.configModel) throw new Error('configModel is required');

    this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
    this._layerDefinitionModel = opts.layerDefinitionModel;
    this._userActions = opts.userActions;
    this._styleModel = this._layerDefinitionModel.styleModel;
    this._configModel = opts.configModel;
    this._modals = opts.modals;
    this._querySchemaModel = opts.querySchemaModel;
    this._editorModel = opts.editorModel;
    this._cartocssModel = this._layerDefinitionModel.cartocssModel;
    this._codemirrorModel = new Backbone.Model({
      content: this._layerDefinitionModel.get('cartocss')
    });

    // Set edition attribute in case custom cartocss is applied
    this._editorModel.set({
      edition: !!this._layerDefinitionModel.get('cartocss_custom'),
      disabled: false
    });

    this._infoboxModel = new InfoboxModel({
      state: this._isLayerHidden() ? 'layer-hidden' : ''
    });

    this._overlayModel = new Backbone.Model({
      visible: this._isLayerHidden()
    });

    this.listenTo(this._editorModel, 'change:edition', this._onChangeEdition);
    this.add_related_model(this._editorModel);

    this._appendMapsAPIError();
    this.listenTo(this._layerDefinitionModel, 'change:error', this._appendMapsAPIError);

    CartoCSSNotifications.track(this);

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

  _appendMapsAPIError: function () {
    var error = this._layerDefinitionModel.get('error');
    if (error) {
      if (error.type === 'turbo-carto') {
        var newErrors = _.clone(this._codemirrorModel.get('errors')) || [];
        newErrors.push({ line: error.line, message: error.message });
        this._codemirrorModel.set('errors', newErrors);
      }
    }
  },

  render: function () {
    this.clearSubViews();
    this.$el.empty();
    this._initViews();
    return this;
  },

  _initBinds: function () {
    this._querySchemaModel.on('change:query_errors', this._updateEditor, this);
    this.add_related_model(this._querySchemaModel);

    this._styleModel.bind('change', function () {
      this._codemirrorModel.set('content', this._layerDefinitionModel.get('cartocss'));
    }, this);
    this.add_related_model(this._styleModel);

    this._cartocssModel.bind('undo redo', function () {
      this._codemirrorModel.set('content', this._cartocssModel.get('content'));
    }, this);
    this.add_related_model(this._cartocssModel);
  },

  _updateEditor: function (model) {
    var errors = this._querySchemaModel.get('query_errors');
    var hasErrors = errors && errors.length > 0;
    this._editorModel.set('disabled', hasErrors);
  },

  _saveCartoCSS: function () {
    var content = this._codemirrorModel.get('content');
    var parser = new ParserCSS(content);
    var errors = parser.errors();

    if (!content) {
      return false;
    }

    this._codemirrorModel.set('errors', parser.parseError(errors));

    if (errors.length === 0) {
      this._cartocssModel.set('content', content);
      this._layerDefinitionModel.set({
        cartocss_custom: true,
        cartocss: content
      });

      this._userActions.saveLayer(this._layerDefinitionModel);

      CartoCSSNotifications.showSuccessNotification();

      MetricsTracker.track('Applied Cartocss', {
        layer_id: this._layerDefinitionModel.get('id'),
        cartocss: this._layerDefinitionModel.get('cartocss')
      });
    } else {
      CartoCSSNotifications.showErrorNotification(parser.parseError(errors));
    }
  },

  _confirmApplyStyles: function () {
    this._infoboxModel.set({state: ''});
    this._overlayModel.set({visible: false});

    this._editorModel.set({
      edition: true
    });
  },

  _cancelApplyStyles: function () {
    this._styleModel.applyLastState();
    this._infoboxModel.set({state: ''});
    this._overlayModel.set({visible: false});
    this.render();
  },

  _cancelTorqueAggregation: function () {
    this._styleModel.applyLastState();
    this._infoboxModel.set({state: ''});
    this._overlayModel.set({visible: false});
    this.render();
  },

  _applyTorqueAggregation: function () {
    if (this._layerDefinitionsCollection.isThereAnyTorqueLayer()) {
      var torqueLayer = this._layerDefinitionsCollection.findWhere({ type: 'torque' });
      torqueLayer.styleModel.setDefaultPropertiesByType('simple', 'point');
    }

    this._infoboxModel.set({state: 'unfreeze'});
  },

  _onChangeEdition: function () {
    this._infoboxState();

    var edition = this._editorModel.get('edition');
    var index = edition ? 1 : 0;
    this._collectionPane.at(index).set({ selected: true });
  },

  _freezeTorgeAggregation: function (styleType, currentGeometryType) {
    this._infoboxModel.set({state: 'torque-exists'});
    this._overlayModel.set({visible: true});

    // Apply the previously selected style
    this._infoboxModel.once('change:state', function (mdl, state) {
      if (state === 'unfreeze') {
        this._moveTorqueLayerToTop(function () {
          this._overlayModel.set({visible: false});
          this._styleModel.setDefaultPropertiesByType(styleType, currentGeometryType);
        }.bind(this));
        this._infoboxModel.set({state: ''});
      }
    }, this);
  },

  _moveTorqueLayerToTop: function (callback) {
    var notification = Notifier.addNotification({
      status: 'loading',
      info: _t('editor.layers.moveTorqueLayer.loading'),
      closable: true
    });

    this._layerDefinitionsCollection.once('layerMoved', function () {
      callback && callback();
      notification.set({
        status: 'success',
        info: _t('editor.layers.moveTorqueLayer.success'),
        delay: Notifier.DEFAULT_DELAY
      });
    }, this);

    this._userActions.moveLayer({
      from: this._layerDefinitionModel.get('order'),
      to: this._layerDefinitionsCollection.getTopDataLayerIndex()
    });
  },

  _configPanes: function () {
    var self = this;
    var tabPaneTabs = [{
      selected: !this._layerDefinitionModel.get('cartocss_custom'),
      createContentView: function () {
        return new ScrollView({
          createContentView: function () {
            return new StyleContentView({
              className: 'Editor-content',
              userActions: self._userActions,
              layerDefinitionsCollection: self._layerDefinitionsCollection,
              layerDefinitionModel: self._layerDefinitionModel,
              styleModel: self._styleModel,
              modals: self._modals,
              configModel: self._configModel,
              querySchemaModel: self._querySchemaModel,
              editorModel: self._editorModel,
              overlayModel: self._overlayModel,
              freezeTorgeAggregation: self._freezeTorgeAggregation.bind(self)
            });
          }
        });
      },
      createActionView: function () {
        return new UndoButtons({
          trackModel: self._styleModel,
          editorModel: self._editorModel,
          applyButton: false
        });
      }
    }, {
      selected: this._layerDefinitionModel.get('cartocss_custom'),
      createContentView: function () {
        return new StyleCartoCSSView({
          layerDefinitionModel: self._layerDefinitionModel,
          styleModel: self._styleModel,
          editorModel: self._editorModel,
          codemirrorModel: self._codemirrorModel,
          onApplyEvent: self._saveCartoCSS.bind(self)
        });
      },
      createActionView: function () {
        return new UndoButtons({
          trackModel: self._cartocssModel,
          editorModel: self._editorModel,
          applyButton: true,
          onApplyClick: self._saveCartoCSS.bind(self)
        });
      }
    }];

    this._collectionPane = new TabPaneCollection(tabPaneTabs);
  },

  _isLayerHidden: function () {
    return this._layerDefinitionModel.get('visible') === false;
  },

  _infoboxState: function () {
    var edition = this._editorModel.get('edition');
    var cartocss_custom = this._layerDefinitionModel.get('cartocss_custom');

    if (!edition && cartocss_custom) {
      this._infoboxModel.set({state: 'confirm'});
      this._overlayModel.set({visible: true});
    } else if (!edition && this._isLayerHidden()) {
      this._infoboxModel.set({state: 'layer-hidden'});
      this._overlayModel.set({visible: true});
    } else {
      this._infoboxModel.set({state: ''});
      this._overlayModel.set({visible: false});
    }
  },

  _showHiddenLayer: function () {
    this._layerDefinitionModel.toggleVisible();
    this._userActions.saveLayer(this._layerDefinitionModel);

    this._infoboxState();
  },

  _initViews: function () {
    var self = this;

    var infoboxSstates = [
      {
        state: 'confirm',
        createContentView: function () {
          return Infobox.createConfirmAndCancel({
            type: 'alert',
            title: _t('editor.style.messages.cartocss-applied.title'),
            body: _t('editor.style.messages.cartocss-applied.body'),
            cancelLabel: _t('editor.style.messages.cartocss-applied.cancel'),
            confirmLabel: _t('editor.style.messages.cartocss-applied.accept')
          });
        },
        mainAction: self._cancelApplyStyles.bind(self),
        secondAction: self._confirmApplyStyles.bind(self)
      }, {
        state: 'layer-hidden',
        createContentView: function () {
          return Infobox.createConfirm({
            type: 'alert',
            title: _t('editor.style.messages.layer-hidden.title'),
            body: _t('editor.style.messages.layer-hidden.body'),
            confirmLabel: _t('editor.style.messages.layer-hidden.show'),
            confirmType: 'button'
          });
        },
        mainAction: self._showHiddenLayer.bind(self)
      }, {
        state: 'torque-exists',
        createContentView: function () {
          return Infobox.createConfirmAndCancel({
            type: 'alert',
            title: _t('editor.style.messages.torque-exists.title'),
            body: _t('editor.style.messages.torque-exists.body'),
            cancelLabel: _t('editor.style.messages.torque-exists.cancel'),
            confirmLabel: _t('editor.style.messages.torque-exists.accept')
          });
        },
        mainAction: self._cancelTorqueAggregation.bind(self),
        secondAction: self._applyTorqueAggregation.bind(self)
      }
    ];

    var infoboxCollection = new InfoboxCollection(infoboxSstates);

    var panelWithOptionsView = new PanelWithOptionsView({
      className: 'Editor-content',
      editorModel: self._editorModel,
      infoboxModel: self._infoboxModel,
      infoboxCollection: infoboxCollection,
      createContentView: function () {
        return new TabPaneView({
          collection: self._collectionPane
        });
      },
      createControlView: function () {
        return new Toggler({
          editorModel: self._editorModel,
          labels: [_t('editor.style.style-toggle.values'), _t('editor.style.style-toggle.cartocss')],
          isDisableable: true
        });
      },
      createActionView: function () {
        return new TabPaneView({
          collection: self._collectionPane,
          createContentKey: 'createActionView'
        });
      }
    });

    this.$el.append(panelWithOptionsView.render().el);
    this.addView(panelWithOptionsView);
  }

});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  function hasClass(el, className) {
    if (el.classList)
      return el.classList.contains(className)
    else
      return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'))
  }

  function my_addClass(el, className) {
    if (el.classList)
      el.classList.add(className)
    else if (!hasClass(el, className)) el.className += " " + className
  }

  function my_removeClass(el, className) {
    if (el.classList)
      el.classList.remove(className)
    else if (hasClass(el, className)) {
      var reg = new RegExp('(\\s|^)' + className + '(\\s|$)')
      el.className=el.className.replace(reg, ' ')
    }
  }

  function scrollTo(element, to, duration) {
    var start = element.scrollTop,
        change = to - start,
        currentTime = 0,
        increment = 20;

    console.log("Move element ", element);
    console.log("to position ", to);

    var animateScroll = function(){
        currentTime += increment;
        var val = Math.easeInOutQuad(currentTime, start, change, duration);
        element.scrollTop = val;
        if(currentTime < duration) {
            setTimeout(animateScroll, increment);
        }
    };
    animateScroll();
  }

  //t = current time
  //b = start value
  //c = change in value
  //d = duration
  Math.easeInOutQuad = function (t, b, c, d) {
    t /= d/2;
    if (t < 1) return c/2*t*t + b;
    t--;
    return -c/2 * (t*(t-2) - 1) + b;
  };

  function each(o, cb, s){
    var n;
    if (!o){
      return 0;
    }
    s = !s ? o : s;
    if (o instanceof Array){
      // Indexed arrays, needed for Safari
      for (n=0; n<o.length; n++) {
        if (cb.call(s, o[n], n, o) === false){
          return 0;
        }
      }
    } else {
      // Hashtables
      for (n in o){
        if (o.hasOwnProperty(n)) {
          if (cb.call(s, o[n], n, o) === false){
            return 0;
          }
        }
      }
    }
    return 1;
  };

  $(document).ready(function() {
    var
      scrollElements,
      inValidRange,
      setVisibility,
      setVisibilityAll,
      resetScroll;

    // scrollElements = $('[data-scroll-element]');
    scrollElements = document.querySelectorAll('[data-scroll-element]');

    inValidRange = function(offset, limit) {
      return offset >= 0 && offset < limit;
    };

    setVisibility = function(element, nextOffset, limit) {
      console.log("Set visibility");
      console.log("nextOffset", nextOffset);
      console.log("limit", limit);
      if(inValidRange(nextOffset, limit)) {
        // element.removeClass('disabled');
        my_removeClass(element, 'disabled');
      } else {
        // element.addClass('disabled');
        my_addClass(element, 'disabled');
      }
    };

    setVisibilityAll = function(elements, newOffset) {
      console.log("setVisibilityAll");
      var
        scrollElement,
        increment,
        nextOffset,
        limit;

      each(elements, function(el) {
        scrollElement = document.querySelector(el.dataset.scrollElement);
        increment  = parseInt(el.dataset.scrollIncrement);
        nextOffset = newOffset + increment;
        limit      = parseInt(scrollElement.dataset.scrollLimit);

        setVisibility(el, nextOffset, limit);
      });
    };

    resetScroll = function(element) {
      element.scrollTop = 0;
      element.dataset.scrollOffset = 0;
    };

    $('[data-clipboard-target]').each(function() {
      var
          clipboard,
          notification;

      clipboard = new Clipboard(this);

      var notify = function(clipboardNotification, notificationText) {
          notification = $($(clipboardNotification));
          notification.text(notificationText);
          notification.addClass('in').delay(1400).queue(function() {
              notification.removeClass('in');
              $(this).dequeue();
          })
      };

      var notifySuccess = function(e) {
          notify(e.trigger.dataset.clipboardNotification, "Copied!");
      };

      var notifyFailure = function(e) {
          //if the copy function failed the text should still be selected, so just ask the user to hit ctrl+c
          notify(e.trigger.dataset.clipboardNotification, "Press Ctrl+C to copy");
      };

      clipboard.on('success', notifySuccess);
      clipboard.on('error', notifyFailure);
    });

    each(scrollElements, function(el) {
      var
        $this,
        element,
        increment,
        limit,
        offset,
        nextOffset,
        newOffset;

      element = document.querySelector(el.dataset.scrollElement);
      increment = parseInt(el.dataset.scrollIncrement);
      limit     = parseInt(element.dataset.scrollLimit.valueOf());
      offset    = parseInt(element.dataset.scrollOffset.valueOf());

      // console.log("increment", increment);
      // console.log("limit", limit);
      // console.log("offset", offset);

      element.dataset.scrollLimit = limit;

      nextOffset = offset + increment;
      // console.log(nextOffset);
      setVisibility(el, nextOffset, limit);

      // Force IE to forget previous scroll top value
      resetScroll(element);

      el.addEventListener('click', function() {
        offset = parseInt(element.dataset.scrollOffset);

        newOffset = offset + increment;

        console.log("ELEMENT scroll top", element);
        if (inValidRange(newOffset, limit)) {
          // element.animate({
          //     scrollTop: document.getElementById(newOffset).offsetTop
          // }, 400);
          // scrollTo(document.getElementById(newOffset), 0, 400);
          element.scrollTop(document.getElementById(newOffset).offsetTop);
          element.dataset.scrollOffset = newOffset;

          setVisibilityAll(scrollElements, newOffset);
        }
      });

      // $this.on('click', function() {
      //   offset = element.dataset.scrollOffset;
      //
      //   newOffset = offset + increment;
      //
      //   console.log("newOffset", newOffset);
      //   if (inValidRange(newOffset, limit)) {
      //     element.animate({
      //         scrollTop: $('#' + newOffset).position().top
      //     }, 400);
      //     element.data('scroll-offset', newOffset);
      //
      //     setVisibilityAll(scrollElements, newOffset);
      //   }
      // });
    })
    // scrollElements.each();

    $('[data-open-panel]').each(function() {
      var
        $this,
        element;

      $this   = $(this);
      element = $($this.data('open-panel'));

      $this.on('click', function() {
        element.addClass('open');
      });
    });

    $('[data-close-panel]').each(function() {
      var
        $this,
        element;

      $this   = $(this);
      element = $($this.data('close-panel'));

      $this.on('click', function() {
        element
          .one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function() {
            $this.trigger('panel:closed');
          }).removeClass('open');
      });
    });

    $('[data-scroll-reset]').each(function() {
      var
        $this,
        element;

      $this   = $(this);
      element = $($this.data('scroll-reset'));

      $this.on('click', function() {
        $this.one('panel:closed', function() {
          resetScroll(element);
          setVisibilityAll(scrollElements, 0);
        });
      });
    });

    $('[data-moment]').each(function() {
      var $this;

      $this = $(this);

      var time = moment(parseInt($this.text()));
      $this.text(time.fromNow());
    });
  });

  var setContainerHeight = function(containerEl) {
    // TODO: Refactor this to make simpler
    var
      bodyEl,
      bodyHeight,
      bodyHeightWithoutTitle,
      titleEl,
      panelEl,
      referralsEl,
      referralsTitleEl,
      panelHeight,
      css,
      stylesheet;

    bodyEl           = $('.squatch-body');
    titleEl          = bodyEl.find('.squatch-title');
    panelEl          = $('#squatch-panel');
    referralsEl      = $('.squatch-referrals');
    referralsTitleEl = $('.squatch-referrals-title');

    bodyHeight = bodyEl.outerHeight();
    bodyHeightWithoutTitle = bodyHeight - titleEl.outerHeight(true) - titleEl.position().top;
    panelHeight = panelEl.outerHeight();

    if (referralsEl.is(':visible')) {
      panelHeight -= referralsEl.outerHeight();
    }

    if (referralsTitleEl.is(':visible')) {
      panelHeight -= referralsTitleEl.outerHeight();
    }

    containerEl.css('height', bodyHeight + panelHeight);

    stylesheet = document.createElement('style');
    stylesheet.type = 'text/css';

    css = '#squatch-panel.open {' +
      '-webkit-transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      '-ms-transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      '-o-transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      'transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      '}' +
      'html.lt-ie9 #squatch-panel.open {' +
      'top: -' + bodyHeightWithoutTitle + 'px;' +
      '}';

    if (stylesheet.styleSheet){
      // IE
      stylesheet.styleSheet.cssText = css;
    } else {
      // W3C Standard
      stylesheet.appendChild(document.createTextNode(css));
    }

    document.querySelector('head').appendChild(stylesheet);
  };

  var containerEl = $('.squatch-container-popup');
  if (containerEl.length) {
    var setContainerHeightForPopup = setContainerHeight.bind(undefined, containerEl);
    var windowEl = $(window);

    // Workaround for popup height being incorrectly set during iframe resizing.
    // This is due to the popup being displayed inconsistently - with responsive styles activated, sometimes the mobile view will be displayed, even on a desktop monitor with more than 500 pixels width. The solution may be to set the iframe width to the full browser width, rather than the width of the widget theme.
    // There is another hack in _popup.less as well
    // TODO: Find a solution for this and enable responsive styles again.
    windowEl.on('load', function (i) {
      var setContainerHeightIfWideEnough = function () {
        var width = windowEl.width();

        if (width === 500) {
          setContainerHeightForPopup();
        } else {
          setTimeout(function() {
            setContainerHeightIfWideEnough();
          }, 50);
        }
      };

      setContainerHeightIfWideEnough();
    });

    // The content has a different height in mobile
    // TODO: Find a a solution for responsive in popups and re-enable this
    // var mql = window.matchMedia('(max-width: 500px)');
    // mql.addListener(setContainerHeightForPopup);
  }
})();
<commit_msg>Trying old scrolltop<commit_after>(function() {
  'use strict';

  function hasClass(el, className) {
    if (el.classList)
      return el.classList.contains(className)
    else
      return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'))
  }

  function my_addClass(el, className) {
    if (el.classList)
      el.classList.add(className)
    else if (!hasClass(el, className)) el.className += " " + className
  }

  function my_removeClass(el, className) {
    if (el.classList)
      el.classList.remove(className)
    else if (hasClass(el, className)) {
      var reg = new RegExp('(\\s|^)' + className + '(\\s|$)')
      el.className=el.className.replace(reg, ' ')
    }
  }

  function scrollTo(element, to, duration) {
    var start = element.scrollTop,
        change = to - start,
        currentTime = 0,
        increment = 20;

    console.log("Move element ", element);
    console.log("to position ", to);

    var animateScroll = function(){
        currentTime += increment;
        var val = Math.easeInOutQuad(currentTime, start, change, duration);
        element.scrollTop = val;
        if(currentTime < duration) {
            setTimeout(animateScroll, increment);
        }
    };
    animateScroll();
  }

  //t = current time
  //b = start value
  //c = change in value
  //d = duration
  Math.easeInOutQuad = function (t, b, c, d) {
    t /= d/2;
    if (t < 1) return c/2*t*t + b;
    t--;
    return -c/2 * (t*(t-2) - 1) + b;
  };

  function each(o, cb, s){
    var n;
    if (!o){
      return 0;
    }
    s = !s ? o : s;
    if (o instanceof Array){
      // Indexed arrays, needed for Safari
      for (n=0; n<o.length; n++) {
        if (cb.call(s, o[n], n, o) === false){
          return 0;
        }
      }
    } else {
      // Hashtables
      for (n in o){
        if (o.hasOwnProperty(n)) {
          if (cb.call(s, o[n], n, o) === false){
            return 0;
          }
        }
      }
    }
    return 1;
  };

  $(document).ready(function() {
    var
      scrollElements,
      inValidRange,
      setVisibility,
      setVisibilityAll,
      resetScroll;

    // scrollElements = $('[data-scroll-element]');
    scrollElements = document.querySelectorAll('[data-scroll-element]');

    inValidRange = function(offset, limit) {
      return offset >= 0 && offset < limit;
    };

    setVisibility = function(element, nextOffset, limit) {
      console.log("Set visibility");
      console.log("nextOffset", nextOffset);
      console.log("limit", limit);
      if(inValidRange(nextOffset, limit)) {
        // element.removeClass('disabled');
        my_removeClass(element, 'disabled');
      } else {
        // element.addClass('disabled');
        my_addClass(element, 'disabled');
      }
    };

    setVisibilityAll = function(elements, newOffset) {
      console.log("setVisibilityAll");
      var
        scrollElement,
        increment,
        nextOffset,
        limit;

      each(elements, function(el) {
        scrollElement = document.querySelector(el.dataset.scrollElement);
        increment  = parseInt(el.dataset.scrollIncrement);
        nextOffset = newOffset + increment;
        limit      = parseInt(scrollElement.dataset.scrollLimit);

        setVisibility(el, nextOffset, limit);
      });
    };

    resetScroll = function(element) {
      element.scrollTop = 0;
      element.dataset.scrollOffset = 0;
    };

    $('[data-clipboard-target]').each(function() {
      var
          clipboard,
          notification;

      clipboard = new Clipboard(this);

      var notify = function(clipboardNotification, notificationText) {
          notification = $($(clipboardNotification));
          notification.text(notificationText);
          notification.addClass('in').delay(1400).queue(function() {
              notification.removeClass('in');
              $(this).dequeue();
          })
      };

      var notifySuccess = function(e) {
          notify(e.trigger.dataset.clipboardNotification, "Copied!");
      };

      var notifyFailure = function(e) {
          //if the copy function failed the text should still be selected, so just ask the user to hit ctrl+c
          notify(e.trigger.dataset.clipboardNotification, "Press Ctrl+C to copy");
      };

      clipboard.on('success', notifySuccess);
      clipboard.on('error', notifyFailure);
    });

    each(scrollElements, function(el) {
      var
        $this,
        element,
        increment,
        limit,
        offset,
        nextOffset,
        newOffset;

      element = document.querySelector(el.dataset.scrollElement);
      increment = parseInt(el.dataset.scrollIncrement);
      limit     = parseInt(element.dataset.scrollLimit.valueOf());
      offset    = parseInt(element.dataset.scrollOffset.valueOf());

      // console.log("increment", increment);
      // console.log("limit", limit);
      // console.log("offset", offset);

      element.dataset.scrollLimit = limit;

      nextOffset = offset + increment;
      // console.log(nextOffset);
      setVisibility(el, nextOffset, limit);

      // Force IE to forget previous scroll top value
      resetScroll(element);

      el.addEventListener('click', function() {
        offset = parseInt(element.dataset.scrollOffset);

        newOffset = offset + increment;

        console.log("ELEMENT scroll top", element);
        if (inValidRange(newOffset, limit)) {
          element.animate({
              scrollTop: document.getElementById(newOffset).offsetTop
          }, 400);
          // scrollTo(document.getElementById(newOffset), 0, 400);
          // element.scrollTop(document.getElementById(newOffset).offsetTop);
          element.dataset.scrollOffset = newOffset;

          setVisibilityAll(scrollElements, newOffset);
        }
      });

      // $this.on('click', function() {
      //   offset = element.dataset.scrollOffset;
      //
      //   newOffset = offset + increment;
      //
      //   console.log("newOffset", newOffset);
      //   if (inValidRange(newOffset, limit)) {
      //     element.animate({
      //         scrollTop: $('#' + newOffset).position().top
      //     }, 400);
      //     element.data('scroll-offset', newOffset);
      //
      //     setVisibilityAll(scrollElements, newOffset);
      //   }
      // });
    })
    // scrollElements.each();

    $('[data-open-panel]').each(function() {
      var
        $this,
        element;

      $this   = $(this);
      element = $($this.data('open-panel'));

      $this.on('click', function() {
        element.addClass('open');
      });
    });

    $('[data-close-panel]').each(function() {
      var
        $this,
        element;

      $this   = $(this);
      element = $($this.data('close-panel'));

      $this.on('click', function() {
        element
          .one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function() {
            $this.trigger('panel:closed');
          }).removeClass('open');
      });
    });

    $('[data-scroll-reset]').each(function() {
      var
        $this,
        element;

      $this   = $(this);
      element = $($this.data('scroll-reset'));

      $this.on('click', function() {
        $this.one('panel:closed', function() {
          resetScroll(element);
          setVisibilityAll(scrollElements, 0);
        });
      });
    });

    $('[data-moment]').each(function() {
      var $this;

      $this = $(this);

      var time = moment(parseInt($this.text()));
      $this.text(time.fromNow());
    });
  });

  var setContainerHeight = function(containerEl) {
    // TODO: Refactor this to make simpler
    var
      bodyEl,
      bodyHeight,
      bodyHeightWithoutTitle,
      titleEl,
      panelEl,
      referralsEl,
      referralsTitleEl,
      panelHeight,
      css,
      stylesheet;

    bodyEl           = $('.squatch-body');
    titleEl          = bodyEl.find('.squatch-title');
    panelEl          = $('#squatch-panel');
    referralsEl      = $('.squatch-referrals');
    referralsTitleEl = $('.squatch-referrals-title');

    bodyHeight = bodyEl.outerHeight();
    bodyHeightWithoutTitle = bodyHeight - titleEl.outerHeight(true) - titleEl.position().top;
    panelHeight = panelEl.outerHeight();

    if (referralsEl.is(':visible')) {
      panelHeight -= referralsEl.outerHeight();
    }

    if (referralsTitleEl.is(':visible')) {
      panelHeight -= referralsTitleEl.outerHeight();
    }

    containerEl.css('height', bodyHeight + panelHeight);

    stylesheet = document.createElement('style');
    stylesheet.type = 'text/css';

    css = '#squatch-panel.open {' +
      '-webkit-transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      '-ms-transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      '-o-transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      'transform: translate(0, -' + bodyHeightWithoutTitle + 'px);' +
      '}' +
      'html.lt-ie9 #squatch-panel.open {' +
      'top: -' + bodyHeightWithoutTitle + 'px;' +
      '}';

    if (stylesheet.styleSheet){
      // IE
      stylesheet.styleSheet.cssText = css;
    } else {
      // W3C Standard
      stylesheet.appendChild(document.createTextNode(css));
    }

    document.querySelector('head').appendChild(stylesheet);
  };

  var containerEl = $('.squatch-container-popup');
  if (containerEl.length) {
    var setContainerHeightForPopup = setContainerHeight.bind(undefined, containerEl);
    var windowEl = $(window);

    // Workaround for popup height being incorrectly set during iframe resizing.
    // This is due to the popup being displayed inconsistently - with responsive styles activated, sometimes the mobile view will be displayed, even on a desktop monitor with more than 500 pixels width. The solution may be to set the iframe width to the full browser width, rather than the width of the widget theme.
    // There is another hack in _popup.less as well
    // TODO: Find a solution for this and enable responsive styles again.
    windowEl.on('load', function (i) {
      var setContainerHeightIfWideEnough = function () {
        var width = windowEl.width();

        if (width === 500) {
          setContainerHeightForPopup();
        } else {
          setTimeout(function() {
            setContainerHeightIfWideEnough();
          }, 50);
        }
      };

      setContainerHeightIfWideEnough();
    });

    // The content has a different height in mobile
    // TODO: Find a a solution for responsive in popups and re-enable this
    // var mql = window.matchMedia('(max-width: 500px)');
    // mql.addListener(setContainerHeightForPopup);
  }
})();
<|endoftext|>


<commit_before>(function (win, $) {
  'use strict';

  $.namespace('win.booktype.editor.settings');

  win.booktype.editor.settings = (function () {

    var SettingsRouter = Backbone.Router.extend({
      routes: {
        'settings': 'settings',
        'settings/:module': 'loadSetting'
      },

      settings: function () {
        var activePanel = win.booktype.editor.getActivePanel();

        activePanel.hide(function () {
          win.booktype.editor.data.activePanel = win.booktype.editor.data.panels['settings'];
          win.booktype.editor.data.activePanel.show();
        });
      },

      loadSetting: function (module) {
        if (win.booktype.editor.settings.uiLoaded === undefined) {
          this.settings();
        }

        // TODO: check if module is valid

        // check if base content has been loaded

        // load corresponding module
        var params = {
          'setting': module,
          'bookid': win.booktype.currentBookID
        };

        win.booktype.ui.notify('Loading section');

        $.get(win.booktype.bookSettingsURL + '?' + $.param(params),
          function (html) {
            $('#setting_content').html(html);
            $('#setting_content').trigger(module + '-loaded');
          }).error(function () {
          var notAvailable = win.booktype.ui.getTemplate('templateModuleNotAvailable');
          $('#setting_content').html(notAvailable);
        }).complete(function () {
          win.booktype.ui.notify();
        });
      }
    });

    var router = new SettingsRouter();

    var _show = function () {
      $('#button-settings').addClass('active');

      var header = win.booktype.ui.getTemplate('templateSettingsHeader');
      $('DIV.contentHeader').html(header);

      var t = win.booktype.ui.getTemplate('templateSettingsContent');
      $('#content').html(t).addClass('settings');

      win.booktype.editor.settings.uiLoaded = true;
    };

    var _hide = function (callback) {
      $('#button-settings').removeClass('active');

      // Destroy tooltip
      $('DIV.contentHeader [rel=tooltip]').tooltip('destroy');

      // Clear content
      $('#content').empty();
      $('DIV.contentHeader').empty();

      if (!_.isUndefined(callback)) {
        callback();
      }
    };

    var _init = function () {
      $('#button-settings').on('click', function () {
        Backbone.history.navigate('settings', true);
      });

      $(document).on('shown.bs.tab', '#settings a[data-toggle="tab"]', function (e) {
        // trigger url via backbone router
        var option = $(e.target).attr('href').replace('#', '');
        router.navigate('settings/' + option, {
          trigger: true
        });
        return false;
      });

      $(document).on('click', '#saveBookSettings', function () {
        var $form = $(this).closest('form');
        var flashMessage = null;

        win.booktype.ui.notify(win.booktype._('loading_data', 'Loading data.'));
        $.ajax({
          type: 'POST',
          url: $form.attr('action'),
          data: $form.serialize(),
          success: function (resp) {
            if (resp.error) {
              if (resp.data !== undefined) {
                $('#setting_content').empty().html(resp.data);
              } else {
                flashMessage = createFlash(resp.message, 'warning');
              }
            } else {
              Backbone.history.navigate('settings', true);
              flashMessage = createFlash(resp.message, 'success');
            }
            win.booktype.ui.notify();
            $('#setting_content').before(flashMessage);
            dismissFlash();
          }
        });

        return false;
      });

      $(document).on('chapter-status-loaded', '#setting_content', function () {
        var $sortable = $('#setting_content .sortable-status');
        var $container = $(this);

        // Chapter status sortable
        $sortable.on('sortstop', function (event, ui) {
          var itemsOrder = _.chain($sortable.sortable('toArray')).where({
            'depth': 1
          }).pluck('item_id').map(function (i) {
            return 'status_id: ' + i;
          }).value();
          win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
          win.booktype.sendToCurrentBook({
            'command': 'book_status_order',
            'order': itemsOrder
          }, function (data) {
            win.booktype.ui.notify();
          });

        });

        $sortable.nestedSortable({
          forcePlaceholderSize: true,
          handle: 'div',
          helper: 'clone',
          items: 'li.item',
          opacity: 0.6,
          placeholder: 'placeholder-status',
          revert: 250,
          tabSize: 25,
          tolerance: 'pointer',
          toleranceElement: '> div',
          maxLevels: 1,
          expandOnHover: 700,
          startCollapsed: true,
          protectRoot: true
        });
        // remove status
        $container.on('click', '.remove-status', function () {
          if (!window.confirm(win.booktype._('chapter_status_remove_confirmation', 'Do you really want to delete this Chapter Status?'))) {
            return;
          }

          var $li = $(this).closest('li.item');
          win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
          win.booktype.sendToCurrentBook({
            'command': 'book_status_remove',
            'status_id': $li.attr('data-id')
          }, function (data) {
            $li.remove();
            win.booktype.ui.notify();
            win.booktype.editor.data.statuses = data.statuses;
          });
        });
        // edit status
        $container.on('click', '.edit-status', function () {
          var el = $(this);
          var statusname = el.closest('li.item').find('.status-name').text();
          el.closest('li.item').find('.status-name').hide();
          el.closest('li.item').find('.edit-status-block').show();
          el.closest('li.item').find('.edit-status-block').find('input').val(statusname);
          el.closest('li.item').find('.edit-status-block').find('input').focus();
        });
        $container.on('click', '.edit-status-ok', function () {
          var $el = $(this),
            $input = $el.closest('.edit-status-block').find('input[type=text]'),
            $li = $el.closest('li.item'),
            statusName = $input.val();

          if (statusName) {
            $input.val('');
            win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
            win.booktype.sendToCurrentBook({
              'command': 'book_status_rename',
              'status_name': statusName,
              'status_id': $li.attr('data-id')
            }, function (data) {
              win.booktype.ui.notify();
              $li.find('.status-name').text(statusName);
              $li.find('.status-name').show();
              $li.find('.edit-status-block').hide();

              win.booktype.editor.data.statuses = data.statuses;
            });
          }
        });
        $container.on('click', '.edit-status-cancel', function () {
          var $el = $(this),
            $li = $el.closest('li.item');

          $li.find('.status-name').text();
          $li.find('.status-name').show();
          $li.find('.edit-status-block').hide();
        });
        // add status
        $container.on('click', '.add-status', function () {
          var $el = $(this),
            $input = $el.closest('.input-group').find('input[type=text]'),
            statusName = $input.val(),
            template = _.template($('#status-item').html());

          if (statusName) {
            $input.val('');
            win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
            win.booktype.sendToCurrentBook({
              'command': 'book_status_create',
              'status_name': statusName
            }, function (data) {
              $sortable.append(template({
                'name': statusName,
                'status_id': data.status_id
              }));
              win.booktype.ui.notify();

              win.booktype.editor.data.statuses = data.statuses;
            });
          }
        });
      });

      // Set a new license Link, when license combobox setting changes
      $(document).on('change', 'SELECT[name=license]', function () {
        var licenseId = $(this).val();
        var licenseList = window.booktype.licenseList;

        if (licenseId in licenseList) {
          $('#license-link').attr('href', licenseList[licenseId]);
        }
      });

      // ----- Roles -----
      $(document).on('mouseenter', '.roles .list li', function () {
        $(this).addClass('show-remove-btn');
      });

      $(document).on('mouseleave', '.roles .list li', function () {
        $(this).removeClass('show-remove-btn');
      });

      // toggle description show and hide
      $(document).on('click', '.toggle-description a', function () {
        $(this).closest('.box').find('.role-description').toggleClass('show');
      });

      // click on a user activates options
      $(document).on('click', '#assignUsers .users-list .list li', function () {
        $(this).parent().children().removeClass('active');
        $(this).toggleClass('active');

        var username = $(this).find('p').html(),
          userRoles = $(this).data('user-roles'),
          roles = $(this).closest('.modal-body').find('.assign-options .roles-options button');

        $(this).closest('.modal-body').find('.assign-options .user-roles span').empty().append(username);

        // reset roles buttons
        $(roles).removeClass('btn-success disabled-tooltip active');
        $(roles).addClass('btn-default');

        // for each role, check if already enable for that user
        $.each(roles, function (_i, role) {
          // remove tooltip for disabled buttons
          unWrapDisabledRole(role);

          var roleId = parseInt($(role).data('role-id'), 10);
          if ($.inArray(roleId, userRoles) === -1) {
            $(role).removeClass('disabled');
          } else {
            $(role)
              .removeClass('btn-default')
              .addClass('disabled-tooltip btn-success disabled');

            wrapDisabledRole(role);
            enableModalTooltips();
          }
        });
      });

      $(document).on('click', '.assign-options .roles-options .btn', function () {
        $(this).closest('.modal-content').find('#assign').removeClass('disabled');
      });

      // reset everything on modal hide
      $(document).on('hidden.bs.modal', '#assignUsers', function () {
        $('.users-list .list li').removeClass('active');
        $('.assign-options .roles-options button').addClass('disabled').removeClass('active');
        $('.assign-options .user-roles span').empty();
        $('#assign').addClass('disabled');
      });

      // calculate the height and apply to user list
      $(document).on('shown.bs.modal', '#assignUsers', function () {
        var optionsHeight = $(this).find('.assign-options').outerHeight();
        $(this).find('.users-list').css('bottom', optionsHeight);

        enableModalTooltips();
      });

      // patching for caise insensitive on jquery contains
      $.expr[':'].contains = $.expr.createPseudo(function (arg) {
        return function (elem) {
          return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
        };
      });

      // we need an inline search for user in assign roles
      $(document).on('keyup', '.search-box input', function () {
        var value = $(this).val();

        $('.users-list > ul > li:not(:contains(' + value + '))').hide();
        $('.users-list > ul > li:contains(' + value + ')').show();
      });

      $(document).on('click', '.search-box .btn', function () {
        $('.search-box input').trigger('keyup');
      });

      // assign user to role
      $(document).on('click', '#assign', function () {
        win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
        var user = $('.users-list > ul > li.active:first').data('user-id'),
          roles = $('.role-btn.active').not('.disabled');

        var rolesid = $.map(roles, function (elem) {
          var roleID = $(elem).data('role-id');
          if (typeof roleID === 'number') {
            return roleID;
          }
        });

        win.booktype.sendToCurrentBook({
            'command': 'assign_to_role',
            'userid': user,
            'roles': rolesid
          },
          function () {
            win.booktype.ui.notify();
            $('#assignUsers').modal('hide');
            Backbone.history.loadUrl('settings/roles');
          }
        );
      });

      // remove user from role
      $(document).on('click', '.remove-btn button', function () {
        var self = this,
          userID = $(this).data('user-id'),
          bookRoleID = $(this).data('bookrole-id'),
          roleID = $(this).data('role-id');

        win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));

        win.booktype.sendToCurrentBook({
            'command': 'remove_user_from_role',
            'userid': userID,
            'roleid': bookRoleID
          },
          function (data) {
            win.booktype.ui.notify();
            if (data.result) {
              $(self).closest('li').remove();
              var userToAssign = $('#assignUsers ul li[data-user-id="' + userID + '"]');
              var newRolesIDS = userToAssign.data('user-roles').filter(function (id) {
                return id !== roleID;
              });
              console.log(newRolesIDS, roleID);
              userToAssign.data('user-roles', newRolesIDS);
            } else {
              win.booktype.ui.notify(win.booktype._('sending_data'));
            }
          }
        );
      });

    };

    var createFlash = function (message, alert) {
      var flashTempl = _.template(
        $('script.templateFlashMessage').html()
      );
      return flashTempl({
        'message': message,
        'alert_type': alert
      });
    };

    var dismissFlash = function () {
      // fade out all alerts with bk-dismiss class.
      $('.bk-dismiss').each(function (i, alert) {
        var timeout = $(alert).data('dismiss-secs');
        setTimeout(function () {
          $(alert)
            .fadeOut(1000, 'linear')
            .remove();
        }, parseInt(timeout, 10) * 1000);
      });
    };

    var enableModalTooltips = function () {
      $('[rel=tooltip]').tooltip({
        container: 'body'
      });
    };

    var wrapDisabledRole = function (roleBtn) {
      var tooltip = win.booktype._('already_in_role', 'Selected user already belongs to this role');
      $(roleBtn).wrap(function () {
        return '

';
      });
    };

    var unWrapDisabledRole = function (roleBtn) {
      if ($(roleBtn).parent().is('div[rel=tooltip]')) {
        $(roleBtn).unwrap();
      }
    };

    return {
      'init': _init,
      'show': _show,
      'hide': _hide,
      'router': router,
      'name': 'settings',
    };
  })();

})(window, jQuery);<commit_msg>BK-1576 UI is broken on publish screen<commit_after>(function (win, $) {
  'use strict';

  $.namespace('win.booktype.editor.settings');

  win.booktype.editor.settings = (function () {

    var SettingsRouter = Backbone.Router.extend({
      routes: {
        'settings': 'settings',
        'settings/:module': 'loadSetting'
      },

      settings: function () {
        var activePanel = win.booktype.editor.getActivePanel();

        activePanel.hide(function () {
          win.booktype.editor.data.activePanel = win.booktype.editor.data.panels['settings'];
          win.booktype.editor.data.activePanel.show();
        });
      },

      loadSetting: function (module) {
        if (win.booktype.editor.settings.uiLoaded === undefined) {
          this.settings();
        }

        // TODO: check if module is valid

        // check if base content has been loaded

        // load corresponding module
        var params = {
          'setting': module,
          'bookid': win.booktype.currentBookID
        };

        win.booktype.ui.notify('Loading section');

        $.get(win.booktype.bookSettingsURL + '?' + $.param(params),
          function (html) {
            $('#setting_content').html(html);
            $('#setting_content').trigger(module + '-loaded');
          }).error(function () {
          var notAvailable = win.booktype.ui.getTemplate('templateModuleNotAvailable');
          $('#setting_content').html(notAvailable);
        }).complete(function () {
          win.booktype.ui.notify();
        });
      }
    });

    var router = new SettingsRouter();

    var _show = function () {
      $('#button-settings').addClass('active');

      var header = win.booktype.ui.getTemplate('templateSettingsHeader');
      $('DIV.contentHeader').html(header);

      var t = win.booktype.ui.getTemplate('templateSettingsContent');
      $('#content').html(t).addClass('settings');

      win.booktype.editor.settings.uiLoaded = true;
    };

    var _hide = function (callback) {
      $('#button-settings').removeClass('active');

      // Destroy tooltip
      $('DIV.contentHeader [rel=tooltip]').tooltip('destroy');

      // Clear content
      $('#content').empty();
      $('#content').removeClass('settings');
      $('DIV.contentHeader').empty();

      if (!_.isUndefined(callback)) {
        callback();
      }
    };

    var _init = function () {
      $('#button-settings').on('click', function () {
        Backbone.history.navigate('settings', true);
      });

      $(document).on('shown.bs.tab', '#settings a[data-toggle="tab"]', function (e) {
        // trigger url via backbone router
        var option = $(e.target).attr('href').replace('#', '');
        router.navigate('settings/' + option, {
          trigger: true
        });
        return false;
      });

      $(document).on('click', '#saveBookSettings', function () {
        var $form = $(this).closest('form');
        var flashMessage = null;

        win.booktype.ui.notify(win.booktype._('loading_data', 'Loading data.'));
        $.ajax({
          type: 'POST',
          url: $form.attr('action'),
          data: $form.serialize(),
          success: function (resp) {
            if (resp.error) {
              if (resp.data !== undefined) {
                $('#setting_content').empty().html(resp.data);
              } else {
                flashMessage = createFlash(resp.message, 'warning');
              }
            } else {
              Backbone.history.navigate('settings', true);
              flashMessage = createFlash(resp.message, 'success');
            }
            win.booktype.ui.notify();
            $('#setting_content').before(flashMessage);
            dismissFlash();
          }
        });

        return false;
      });

      $(document).on('chapter-status-loaded', '#setting_content', function () {
        var $sortable = $('#setting_content .sortable-status');
        var $container = $(this);

        // Chapter status sortable
        $sortable.on('sortstop', function (event, ui) {
          var itemsOrder = _.chain($sortable.sortable('toArray')).where({
            'depth': 1
          }).pluck('item_id').map(function (i) {
            return 'status_id: ' + i;
          }).value();
          win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
          win.booktype.sendToCurrentBook({
            'command': 'book_status_order',
            'order': itemsOrder
          }, function (data) {
            win.booktype.ui.notify();
          });

        });

        $sortable.nestedSortable({
          forcePlaceholderSize: true,
          handle: 'div',
          helper: 'clone',
          items: 'li.item',
          opacity: 0.6,
          placeholder: 'placeholder-status',
          revert: 250,
          tabSize: 25,
          tolerance: 'pointer',
          toleranceElement: '> div',
          maxLevels: 1,
          expandOnHover: 700,
          startCollapsed: true,
          protectRoot: true
        });
        // remove status
        $container.on('click', '.remove-status', function () {
          if (!window.confirm(win.booktype._('chapter_status_remove_confirmation', 'Do you really want to delete this Chapter Status?'))) {
            return;
          }

          var $li = $(this).closest('li.item');
          win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
          win.booktype.sendToCurrentBook({
            'command': 'book_status_remove',
            'status_id': $li.attr('data-id')
          }, function (data) {
            $li.remove();
            win.booktype.ui.notify();
            win.booktype.editor.data.statuses = data.statuses;
          });
        });
        // edit status
        $container.on('click', '.edit-status', function () {
          var el = $(this);
          var statusname = el.closest('li.item').find('.status-name').text();
          el.closest('li.item').find('.status-name').hide();
          el.closest('li.item').find('.edit-status-block').show();
          el.closest('li.item').find('.edit-status-block').find('input').val(statusname);
          el.closest('li.item').find('.edit-status-block').find('input').focus();
        });
        $container.on('click', '.edit-status-ok', function () {
          var $el = $(this),
            $input = $el.closest('.edit-status-block').find('input[type=text]'),
            $li = $el.closest('li.item'),
            statusName = $input.val();

          if (statusName) {
            $input.val('');
            win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
            win.booktype.sendToCurrentBook({
              'command': 'book_status_rename',
              'status_name': statusName,
              'status_id': $li.attr('data-id')
            }, function (data) {
              win.booktype.ui.notify();
              $li.find('.status-name').text(statusName);
              $li.find('.status-name').show();
              $li.find('.edit-status-block').hide();

              win.booktype.editor.data.statuses = data.statuses;
            });
          }
        });
        $container.on('click', '.edit-status-cancel', function () {
          var $el = $(this),
            $li = $el.closest('li.item');

          $li.find('.status-name').text();
          $li.find('.status-name').show();
          $li.find('.edit-status-block').hide();
        });
        // add status
        $container.on('click', '.add-status', function () {
          var $el = $(this),
            $input = $el.closest('.input-group').find('input[type=text]'),
            statusName = $input.val(),
            template = _.template($('#status-item').html());

          if (statusName) {
            $input.val('');
            win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
            win.booktype.sendToCurrentBook({
              'command': 'book_status_create',
              'status_name': statusName
            }, function (data) {
              $sortable.append(template({
                'name': statusName,
                'status_id': data.status_id
              }));
              win.booktype.ui.notify();

              win.booktype.editor.data.statuses = data.statuses;
            });
          }
        });
      });

      // Set a new license Link, when license combobox setting changes
      $(document).on('change', 'SELECT[name=license]', function () {
        var licenseId = $(this).val();
        var licenseList = window.booktype.licenseList;

        if (licenseId in licenseList) {
          $('#license-link').attr('href', licenseList[licenseId]);
        }
      });

      // ----- Roles -----
      $(document).on('mouseenter', '.roles .list li', function () {
        $(this).addClass('show-remove-btn');
      });

      $(document).on('mouseleave', '.roles .list li', function () {
        $(this).removeClass('show-remove-btn');
      });

      // toggle description show and hide
      $(document).on('click', '.toggle-description a', function () {
        $(this).closest('.box').find('.role-description').toggleClass('show');
      });

      // click on a user activates options
      $(document).on('click', '#assignUsers .users-list .list li', function () {
        $(this).parent().children().removeClass('active');
        $(this).toggleClass('active');

        var username = $(this).find('p').html(),
          userRoles = $(this).data('user-roles'),
          roles = $(this).closest('.modal-body').find('.assign-options .roles-options button');

        $(this).closest('.modal-body').find('.assign-options .user-roles span').empty().append(username);

        // reset roles buttons
        $(roles).removeClass('btn-success disabled-tooltip active');
        $(roles).addClass('btn-default');

        // for each role, check if already enable for that user
        $.each(roles, function (_i, role) {
          // remove tooltip for disabled buttons
          unWrapDisabledRole(role);

          var roleId = parseInt($(role).data('role-id'), 10);
          if ($.inArray(roleId, userRoles) === -1) {
            $(role).removeClass('disabled');
          } else {
            $(role)
              .removeClass('btn-default')
              .addClass('disabled-tooltip btn-success disabled');

            wrapDisabledRole(role);
            enableModalTooltips();
          }
        });
      });

      $(document).on('click', '.assign-options .roles-options .btn', function () {
        $(this).closest('.modal-content').find('#assign').removeClass('disabled');
      });

      // reset everything on modal hide
      $(document).on('hidden.bs.modal', '#assignUsers', function () {
        $('.users-list .list li').removeClass('active');
        $('.assign-options .roles-options button').addClass('disabled').removeClass('active');
        $('.assign-options .user-roles span').empty();
        $('#assign').addClass('disabled');
      });

      // calculate the height and apply to user list
      $(document).on('shown.bs.modal', '#assignUsers', function () {
        var optionsHeight = $(this).find('.assign-options').outerHeight();
        $(this).find('.users-list').css('bottom', optionsHeight);

        enableModalTooltips();
      });

      // patching for caise insensitive on jquery contains
      $.expr[':'].contains = $.expr.createPseudo(function (arg) {
        return function (elem) {
          return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
        };
      });

      // we need an inline search for user in assign roles
      $(document).on('keyup', '.search-box input', function () {
        var value = $(this).val();

        $('.users-list > ul > li:not(:contains(' + value + '))').hide();
        $('.users-list > ul > li:contains(' + value + ')').show();
      });

      $(document).on('click', '.search-box .btn', function () {
        $('.search-box input').trigger('keyup');
      });

      // assign user to role
      $(document).on('click', '#assign', function () {
        win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));
        var user = $('.users-list > ul > li.active:first').data('user-id'),
          roles = $('.role-btn.active').not('.disabled');

        var rolesid = $.map(roles, function (elem) {
          var roleID = $(elem).data('role-id');
          if (typeof roleID === 'number') {
            return roleID;
          }
        });

        win.booktype.sendToCurrentBook({
            'command': 'assign_to_role',
            'userid': user,
            'roles': rolesid
          },
          function () {
            win.booktype.ui.notify();
            $('#assignUsers').modal('hide');
            Backbone.history.loadUrl('settings/roles');
          }
        );
      });

      // remove user from role
      $(document).on('click', '.remove-btn button', function () {
        var self = this,
          userID = $(this).data('user-id'),
          bookRoleID = $(this).data('bookrole-id'),
          roleID = $(this).data('role-id');

        win.booktype.ui.notify(win.booktype._('sending_data', 'Sending data.'));

        win.booktype.sendToCurrentBook({
            'command': 'remove_user_from_role',
            'userid': userID,
            'roleid': bookRoleID
          },
          function (data) {
            win.booktype.ui.notify();
            if (data.result) {
              $(self).closest('li').remove();
              var userToAssign = $('#assignUsers ul li[data-user-id="' + userID + '"]');
              var newRolesIDS = userToAssign.data('user-roles').filter(function (id) {
                return id !== roleID;
              });
              console.log(newRolesIDS, roleID);
              userToAssign.data('user-roles', newRolesIDS);
            } else {
              win.booktype.ui.notify(win.booktype._('sending_data'));
            }
          }
        );
      });

    };

    var createFlash = function (message, alert) {
      var flashTempl = _.template(
        $('script.templateFlashMessage').html()
      );
      return flashTempl({
        'message': message,
        'alert_type': alert
      });
    };

    var dismissFlash = function () {
      // fade out all alerts with bk-dismiss class.
      $('.bk-dismiss').each(function (i, alert) {
        var timeout = $(alert).data('dismiss-secs');
        setTimeout(function () {
          $(alert)
            .fadeOut(1000, 'linear')
            .remove();
        }, parseInt(timeout, 10) * 1000);
      });
    };

    var enableModalTooltips = function () {
      $('[rel=tooltip]').tooltip({
        container: 'body'
      });
    };

    var wrapDisabledRole = function (roleBtn) {
      var tooltip = win.booktype._('already_in_role', 'Selected user already belongs to this role');
      $(roleBtn).wrap(function () {
        return '

';
      });
    };

    var unWrapDisabledRole = function (roleBtn) {
      if ($(roleBtn).parent().is('div[rel=tooltip]')) {
        $(roleBtn).unwrap();
      }
    };

    return {
      'init': _init,
      'show': _show,
      'hide': _hide,
      'router': router,
      'name': 'settings',
    };
  })();

})(window, jQuery);<|endoftext|>


<commit_before><commit_msg>[Evo] #9164 : redirect after edit<commit_after><|endoftext|>


<commit_before>import { resolve } from 'rsvp';
import { get } from '@ember/object';
import Reference from './reference';
import { DEBUG } from '@glimmer/env';
import { assertPolymorphicType } from 'ember-data/-debug';

/**
 A HasManyReference is a low-level API that allows users and addon
 author to perform meta-operations on a has-many relationship.

 @class HasManyReference
 @namespace DS
 */
export default class HasManyReference extends Reference {
  constructor(store, parentInternalModel, hasManyRelationship, key) {
    super(store, parentInternalModel);
    this.key = key;
    this.hasManyRelationship = hasManyRelationship;
    this.type = hasManyRelationship.relationshipMeta.type;
    this.parent = parentInternalModel.recordReference;
    this.parentInternalModel = parentInternalModel;

    // TODO inverse
  }

  _resource() {
    return this.recordData.getHasMany(this.key);
  }

  /**
   This returns a string that represents how the reference will be
   looked up when it is loaded. If the relationship has a link it will
   use the "link" otherwise it defaults to "id".

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   // get the identifier of the reference
   if (commentsRef.remoteType() === "ids") {
     let ids = commentsRef.ids();
   } else if (commentsRef.remoteType() === "link") {
     let link = commentsRef.link();
   }
   ```

   @method remoteType
   @return {String} The name of the remote type. This should either be `link` or `ids`
   */
  remoteType() {
    let value = this._resource();
    if (value && value.links && value.links.related) {
      return 'link';
    }

    return 'ids';
  }

  /**
   `ids()` returns an array of the record IDs in this relationship.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.ids(); // ['1']
   ```

   @method ids
   @return {Array} The ids in this has-many relationship
   */
  ids() {
    let resource = this._resource();

    let ids = [];
    if (resource.data) {
      ids = resource.data.map(data => data.id);
    }

    return ids;
  }

  /**
   `push` can be used to update the data in the relationship and Ember
   Data will treat the new data as the canonical value of this
   relationship on the backend.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.ids(); // ['1']

   commentsRef.push([
   [{ type: 'comment', id: 2 }],
   [{ type: 'comment', id: 3 }],
   ])

   commentsRef.ids(); // ['2', '3']
   ```

   @method push
   @param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
   @return {DS.ManyArray}
   */
  push(objectOrPromise) {
    return resolve(objectOrPromise).then(payload => {
      let array = payload;

      if (typeof payload === 'object' && payload.data) {
        array = payload.data;
      }

      let internalModels = array.map(obj => {
        let record = this.store.push(obj);

        if (DEBUG) {
          let relationshipMeta = this.hasManyRelationship.relationshipMeta;
          assertPolymorphicType(
            this.internalModel,
            relationshipMeta,
            record._internalModel,
            this.store
          );
        }
        return record._internalModel._recordData;
      });

      this.hasManyRelationship.computeChanges(internalModels);

      return this.internalModel.getHasMany(this.hasManyRelationship.key);
      // TODO IGOR it seems wrong that we were returning the many array here
      //return this.hasManyRelationship.manyArray;
    });
  }

  _isLoaded() {
    let hasRelationshipDataProperty = get(this.hasManyRelationship, 'hasAnyRelationshipData');
    if (!hasRelationshipDataProperty) {
      return false;
    }

    let members = this.hasManyRelationship.members.toArray();

    //TODO Igor cleanup
    return members.every(recordData => {
      let store = this.parentInternalModel.store;
      let internalModel = store._internalModelForRecordData(recordData);
      return internalModel.isLoaded() === true;
    });
  }

  /**
   `value()` synchronously returns the current value of the has-many
   relationship. Unlike `record.get('relationshipName')`, calling
   `value()` on a reference does not trigger a fetch if the async
   relationship is not yet loaded. If the relationship is not loaded
   it will always return `null`.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   post.get('comments').then(function(comments) {
     commentsRef.value() === comments
   })
   ```

   @method value
   @return {DS.ManyArray}
   */
  value() {
    if (this._isLoaded()) {
      return this.internalModel.getManyArray(this.key);
    }

    return null;
  }

  /**
   Loads the relationship if it is not already loaded.  If the
   relationship is already loaded this method does not trigger a new
   load.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.load().then(function(comments) {
     //...
   });
   ```

   You may also pass in an options object whose properties will be
   fed forward. This enables you to pass `adapterOptions` into the
   request given to the adapter via the reference.

   Example

   ```javascript
   commentsRef.load({ adapterOptions: { isPrivate: true } })
     .then(function(comments) {
       //...
     });
   ```

   ```app/adapters/comment.js
   export default ApplicationAdapter.extend({
     findMany(store, type, id, snapshots) {
       // In the adapter you will have access to adapterOptions.
       let adapterOptions = snapshots[0].adapterOptions;
     }
   });
   ```

   @method load
   @param {Object} options the options to pass in.
   @return {Promise} a promise that resolves with the ManyArray in
   this has-many relationship.
   */
  load(options) {
    return this.internalModel.getHasMany(this.key, options);
  }

  /**
   Reloads this has-many relationship.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.reload().then(function(comments) {
     //...
   });
   ```

   You may also pass in an options object whose properties will be
   fed forward. This enables you to pass `adapterOptions` into the
   request given to the adapter via the reference. A full example
   can be found in the `load` method.

   Example

   ```javascript
   commentsRef.reload({ adapterOptions: { isPrivate: true } })
   ```

   @method reload
   @param {Object} options the options to pass in.
   @return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
   */
  reload(options) {
    return this.internalModel.reloadHasMany(this.key, options);
  }
}
<commit_msg>[DOCS has-many]: clarify load/reload behavior<commit_after>import { resolve } from 'rsvp';
import { get } from '@ember/object';
import Reference from './reference';
import { DEBUG } from '@glimmer/env';
import { assertPolymorphicType } from 'ember-data/-debug';

/**
 A HasManyReference is a low-level API that allows users and addon
 author to perform meta-operations on a has-many relationship.

 @class HasManyReference
 @namespace DS
 */
export default class HasManyReference extends Reference {
  constructor(store, parentInternalModel, hasManyRelationship, key) {
    super(store, parentInternalModel);
    this.key = key;
    this.hasManyRelationship = hasManyRelationship;
    this.type = hasManyRelationship.relationshipMeta.type;
    this.parent = parentInternalModel.recordReference;
    this.parentInternalModel = parentInternalModel;

    // TODO inverse
  }

  _resource() {
    return this.recordData.getHasMany(this.key);
  }

  /**
   This returns a string that represents how the reference will be
   looked up when it is loaded. If the relationship has a link it will
   use the "link" otherwise it defaults to "id".

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   // get the identifier of the reference
   if (commentsRef.remoteType() === "ids") {
     let ids = commentsRef.ids();
   } else if (commentsRef.remoteType() === "link") {
     let link = commentsRef.link();
   }
   ```

   @method remoteType
   @return {String} The name of the remote type. This should either be `link` or `ids`
   */
  remoteType() {
    let value = this._resource();
    if (value && value.links && value.links.related) {
      return 'link';
    }

    return 'ids';
  }

  /**
   `ids()` returns an array of the record IDs in this relationship.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.ids(); // ['1']
   ```

   @method ids
   @return {Array} The ids in this has-many relationship
   */
  ids() {
    let resource = this._resource();

    let ids = [];
    if (resource.data) {
      ids = resource.data.map(data => data.id);
    }

    return ids;
  }

  /**
   `push` can be used to update the data in the relationship and Ember
   Data will treat the new data as the canonical value of this
   relationship on the backend.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.ids(); // ['1']

   commentsRef.push([
   [{ type: 'comment', id: 2 }],
   [{ type: 'comment', id: 3 }],
   ])

   commentsRef.ids(); // ['2', '3']
   ```

   @method push
   @param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
   @return {DS.ManyArray}
   */
  push(objectOrPromise) {
    return resolve(objectOrPromise).then(payload => {
      let array = payload;

      if (typeof payload === 'object' && payload.data) {
        array = payload.data;
      }

      let internalModels = array.map(obj => {
        let record = this.store.push(obj);

        if (DEBUG) {
          let relationshipMeta = this.hasManyRelationship.relationshipMeta;
          assertPolymorphicType(
            this.internalModel,
            relationshipMeta,
            record._internalModel,
            this.store
          );
        }
        return record._internalModel._recordData;
      });

      this.hasManyRelationship.computeChanges(internalModels);

      return this.internalModel.getHasMany(this.hasManyRelationship.key);
      // TODO IGOR it seems wrong that we were returning the many array here
      //return this.hasManyRelationship.manyArray;
    });
  }

  _isLoaded() {
    let hasRelationshipDataProperty = get(this.hasManyRelationship, 'hasAnyRelationshipData');
    if (!hasRelationshipDataProperty) {
      return false;
    }

    let members = this.hasManyRelationship.members.toArray();

    //TODO Igor cleanup
    return members.every(recordData => {
      let store = this.parentInternalModel.store;
      let internalModel = store._internalModelForRecordData(recordData);
      return internalModel.isLoaded() === true;
    });
  }

  /**
   `value()` synchronously returns the current value of the has-many
   relationship. Unlike `record.get('relationshipName')`, calling
   `value()` on a reference does not trigger a fetch if the async
   relationship is not yet loaded. If the relationship is not loaded
   it will always return `null`.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   post.get('comments').then(function(comments) {
     commentsRef.value() === comments
   })
   ```

   @method value
   @return {DS.ManyArray}
   */
  value() {
    if (this._isLoaded()) {
      return this.internalModel.getManyArray(this.key);
    }

    return null;
  }

  /**
   Loads the relationship if it is not already loaded.  If the
   relationship is already loaded this method does not trigger a new
   load. This causes a request to the specified
   relationship link or reloads all items currently in the relationship.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.load().then(function(comments) {
     //...
   });
   ```

   You may also pass in an options object whose properties will be
   fed forward. This enables you to pass `adapterOptions` into the
   request given to the adapter via the reference.

   Example

   ```javascript
   commentsRef.load({ adapterOptions: { isPrivate: true } })
     .then(function(comments) {
       //...
     });
   ```

   ```app/adapters/comment.js
   export default ApplicationAdapter.extend({
     findMany(store, type, id, snapshots) {
       // In the adapter you will have access to adapterOptions.
       let adapterOptions = snapshots[0].adapterOptions;
     }
   });
   ```

   @method load
   @param {Object} options the options to pass in.
   @return {Promise} a promise that resolves with the ManyArray in
   this has-many relationship.
   */
  load(options) {
    return this.internalModel.getHasMany(this.key, options);
  }

  /**
   Reloads this has-many relationship. This causes a request to the specified
   relationship link or reloads all items currently in the relationship.

   Example

   ```app/models/post.js
   export default DS.Model.extend({
     comments: DS.hasMany({ async: true })
   });
   ```

   ```javascript
   let post = store.push({
     data: {
       type: 'post',
       id: 1,
       relationships: {
         comments: {
           data: [{ type: 'comment', id: 1 }]
         }
       }
     }
   });

   let commentsRef = post.hasMany('comments');

   commentsRef.reload().then(function(comments) {
     //...
   });
   ```

   You may also pass in an options object whose properties will be
   fed forward. This enables you to pass `adapterOptions` into the
   request given to the adapter via the reference. A full example
   can be found in the `load` method.

   Example

   ```javascript
   commentsRef.reload({ adapterOptions: { isPrivate: true } })
   ```

   @method reload
   @param {Object} options the options to pass in.
   @return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
   */
  reload(options) {
    return this.internalModel.reloadHasMany(this.key, options);
  }
}
<|endoftext|>