define(['errors', 'IPlugin', 'screenModel', 'animation', 'screenManager', 'baseDispatcher', 'controlManager', 'swipesControl', 'arrowsControl', 'keydownControl', 'elementsPool', 'utils'], function(
Errors, IPlugin, ScreenModel, Animation, ScreenManager, BaseDispatcher, ControlManager, SwipesControl, ArrowsControl, KeydownControl, ElementsPool, Utils) {
"use strict";
var sides = Utils.sidesWithCenter;
/**
* @class
* Класс управления панелью
* @param {JQuery} mainDiv - элемент, в котором располагается панель. Должен содержать класс rb-wrapper.
* @param {ScreenModel} startScreen - стартовая модель контента
* @constructor Moving
*/
function Moving(mainDiv, startScreen) {
if (mainDiv instanceof $) {
this._mainDiv = mainDiv;
} else {
throw new Errors.ArgumentError('mainDiv', mainDiv);
}
/**
* Диспетчер, выполняющий зарегистрированные функции до выполнения перехода к новой ячейке в панели
* Функциям в аргументы передается side, instance текущей панели и флаг isWrongStep - удачный переход или нет.
* Если флаг равен null, то осуществляется moveBack.
* Функция может вернуть флаг или Promise. Если флаг равен false или Promise возвращает false, то переход не будет осуществлен.
* @name beforeMoveDispatcher
* @memberOf Moving#
*/
this.beforeMoveDispatcher = new BaseDispatcher(mainDiv);
/**
* Диспетчер, выполняющий зарегистрированные функции до рендеринга контента моделей на странице после перехода.
* Функциям в аргументы передается side и instance текущей панели.
* @name beforeRenderDispatcher
* @memberOf Moving#
*/
this.beforeRenderDispatcher = new BaseDispatcher(mainDiv);
/**
* Диспетчер, выполняющий зарегистрированные функции после рендеринга контента моделей на странице после перехода
* Функциям в аргументы передается side и instance текущей панели.
* @name afterRenderDispatcher
* @memberOf Moving#
*/
this.afterRenderDispatcher = new BaseDispatcher(mainDiv);
this._screenManager = new ScreenManager();
this._elementsPool = new ElementsPool(mainDiv, this._screenManager);
this._animation = new Animation(mainDiv, this._elementsPool);
this._controlManager = new ControlManager();
if (Utils.isMobile) {
this._controlManager
.add('swipes', new SwipesControl(mainDiv,this._moveByActionValue.bind(this)));
} else {
this._controlManager
.add('arrows', new ArrowsControl(mainDiv, this._moveByActionValue.bind(this), this.afterRenderDispatcher))
.add('keyboard', new KeydownControl(mainDiv, this._moveByActionValue.bind(this)));
}
this.elementLoadedDispatcher = this._elementsPool.elementLoadedDispatcher;
this.elementUnloadedDispatcher = this._elementsPool.elementUnloadedDispatcher;
this._plugins = [];
this.resetConfig();
this._loadingPromise = this.setScreen(startScreen || ScreenModel.getMainScreen(), false);
this._controlManager.enableAll();
//if (mainDiv.length) {
// mainDiv[0].moving = this;
//}
var self = this;
this._clickHandler = function(e) {
if ($(e.target).closest('.rb').length && !$(document.activeElement).closest('.rb').length) {
self.activate();
}
};
mainDiv.on('click', this._clickHandler);
this._relativeUpdateFn = this._reloadScreen.bind(this);
ScreenModel.registerUpdateFn(this._relativeUpdateFn);
}
/**
* Сбросить конфигурацию панели к значению по умолчанию (какое именно - см. в коде).
* @memberOf Moving
*/
Moving.prototype.resetConfig = function() {
this.configure({
wrongTime1: 500,
wrongTime2: 500,
correctTime: 1000,
wrongEasing1: 'easeInExpo',
wrongEasing2: 'easeOutElastic',
correctEasing: 'easeOutExpo',
hideArrowsAfterTime: true,
hideArrowsTime: 2000,
showArrowsOutside: true,
showArrowsOnHover: true,
loadingHtml: '<div class="rb__loading_wrapper"><div class="rb__loader"></div></div>',
//http://www.javascripter.net/faq/keycodes.htm
//https://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
leftKey: [37, 'a'],
topKey: [38, 'w'],
rightKey: [39, 'd'],
bottomKey: [40, 's'],
maxHistoryLength: 10,
lockControls: false,
showAdjacentScreens: true,
saveHistoryInPool: false,
pointersForSwipe: 1,
isDirectPath: true,
savePrevious: true,
cyclicStep: true,
getRight: function(screen) {
var childIndex = screen.defaultChildIndex();
return screen.getChild(childIndex);
},
getLeft: function(screen) {
var parentIndex = screen.defaultParentIndex();
return screen.getParent(parentIndex);
},
getTop: function(screen, cyclicStep) {
function getNextIndex(index, length) {
return cyclicStep ? Utils.cycledNumber(index - 1, length) : (index - 1);
}
var index;
if (this._lastSide === 'left') {
index = this._lastScreen.getParentIndex(screen);
return this._lastScreen.getParent(getNextIndex(index, this._lastScreen.parentsLength()));
}
if (this._lastSide === 'right') {
index = this._lastScreen.getChildIndex(screen);
return this._lastScreen.getChild(getNextIndex(index, this._lastScreen.childrenLength()));
}
var parent = this._getLeft(screen);
if (parent) {
index = parent.getChildIndex(screen);
return parent.getChild(getNextIndex(index, parent.childrenLength()));
}
var child = this._getRight(screen);
if (child) {
index = child.getParentIndex(screen);
return child.getParent(getNextIndex(index, child.parentsLength()));
}
},
getBottom: function(screen, cyclicStep) {
function getNextIndex(index, length) {
return cyclicStep ? Utils.cycledNumber(index + 1, length) : (index + 1);
}
var index;
if (this._lastSide === 'left') {
index = this._lastScreen.getParentIndex(screen);
return this._lastScreen.getParent(getNextIndex(index, this._lastScreen.parentsLength()));
}
if (this._lastSide === 'right') {
index = this._lastScreen.getChildIndex(screen);
return this._lastScreen.getChild(getNextIndex(index, this._lastScreen.childrenLength()));
}
var parent = this._getLeft(screen);
if (parent) {
index = parent.getChildIndex(screen);
return parent.getChild(getNextIndex(index, parent.childrenLength()));
}
var child = this._getRight(screen);
if (child) {
index = child.getParentIndex(screen);
return child.getParent(getNextIndex(index, child.parentsLength()));
}
}
});
};
/**
* @typedef {function} Moving~getLeft
* Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход влево, то есть в сторону предка.
* По умолчанию берет опцию defaultParentIndex у экземпляра ScreenModel и ищет предка модели с этим индексом.
* @param {ScreenModel} screen - текущая модель контента панели
*/
/**
* @typedef {function} Moving~getTop
* Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход вверх, то есть в сторону соседа сверху.
* По умолчанию смотрит, осуществлялись ли переходы влево-вправо,
* если последним таким был переход вправо, то берется модель, из которой был сделан шаг вправо и в контексте ее потомков
* от текущего потомка будет найден предыдущий потомок и возвращен в качестве результата.
* Если при этом стоит опция cyclicStep = true, то в случае самого верхнего потомка следующим будет возвращен самый нижний потомок.
* Если последним был переход влево, то берется модель, из которой был сделан шаг влево и в контексте ее предков
* от текущего предка будет найден предыдущий предок и возвращен в качестве результата.
* Если при этом стоит опция cyclicStep = true, то в случае самого верхнего предка следующим будет возвращен самый нижний предок.
* А если переходов влево-вправо не было, будет считаться, будто бы был сделан переход вправо, а если и это не помогло,
* будет считаться, будто был сделан переход влево.
* Если у модели нет предков, следующая модель не будет найдена.
* @param {ScreenModel} screen - текущая модель контента панели
* @param {boolean} cyclicStep - делать ли цикличный переход в контексте массива моделей, в котором будет искаться новая модель.
*/
/**
* @typedef {function} Moving~getRight
* Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход вправо, то есть в сторону потомка.
* По умолчанию берет опцию defaultChildIndex у экземпляра ScreenModel и ищет потомка модели с этим индексом.
* @param {ScreenModel} screen - текущая модель контента панели
*/
/**
* @typedef {function} Moving~getBottom
* Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход вниз, то есть в сторону соседа снизу.
* По умолчанию смотрит, осуществлялись ли переходы влево-вправо,
* если последним таким был переход вправо, то берется модель, из которой был сделан шаг вправо и в контексте ее потомков
* от текущего потомка будет найден следующий потомок и возвращен в качестве результата.
* Если при этом стоит опция cyclicStep = true, то в случае самого нижнего потомка следующим будет возвращен самый верхний потомок.
* Если последним был переход влево, то берется модель, из которой был сделан шаг влево и в контексте ее предков
* от текущего предка будет найден следующий предок и возвращен в качестве результата.
* Если при этом стоит опция cyclicStep = true, то в случае самого нижнего предка следующим будет возвращен самый верхний предок.
* А если переходов влево-вправо не было, будет считаться, будто бы был сделан переход вправо, а если и это не помогло,
* будет считаться, будто был сделан переход влево.
* Если у модели нет предков, следующая модель не будет найдена.
* @param {ScreenModel} screen - текущая модель контента панели
* @param {boolean} cyclicStep - делать ли цикличный переход в контексте массива моделей, в котором будет искаться новая модель.
*/
/**
* Конфигурация панели
* @typedef {Object} Moving~config
* @property {number} [wrongTime1] - Время, затрачиваемое на первую часть неудачного перехода
* @property {number} [wrongTime2] - Время, затрачиваемое на вторую часть неудачного перехода
* @property {number} [correctTime] - Время, затрачиваемое на удачный переход
* @property {string} [wrongEasing1] - Анимация первой части неудачного перехода.<br> jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
* @property {string} [wrongEasing2] - Анимация второй части неудачного перехода.<br> jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
* @property {string} [correctEasing] - Анимация удачного перехода.<br> jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
* @property {boolean} [hideArrowsAfterTime] - Скрывать ли стрелки перемещения после некоторого времени, чтобы можно было нажимать на элементы, которые находятся под этими стрелками.
* @property {number} [hideArrowsTime] - Через какой промежуток времени скрывать стрелки перемещения
* @property {boolean} [showArrowsOutside] - Показывать ли стрелки перемещения снаружи от панели (иначе показывать внутри панели)
* @property {boolean} [showArrowsOnHover] - Показывать ли стрелки при наведении мыши (иначе показывать всегда)
* @property {string|null} [loadingHtml] - Верстка ожидания, показываемая во время, пока контент еще не был вставлен в панель
* @property {(string|number|Array.<string|number>)} [leftKey] - Клавиши, при нажатии на которые будет сделан переход влево.
* Могут использоваться строковые или числовые нотации клавиш.<br>
* http://www.javascripter.net/faq/keycodes.htm<br>
* https://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
* @property {(string|number|Array.<string|number>)} [topKey] - Клавиши, при нажатии на которые будет сделан переход вверх.
* @property {(string|number|Array.<string|number>)} [rightKey] - Клавиши, при нажатии на которые будет сделан переход вправо.
* @property {(string|number|Array.<string|number>)} [bottomKey] - Клавиши, при нажатии на которые будет сделан переход вниз.
* @property {number} [maxHistoryLength] - Максимальная длина хранимой истории удачных переходов.
* @property {boolean} [lockControls] - Заблокированы ли элементы управления переходами, пока происходит анимация перехода (любые способы управления).
* @property {boolean} [showAdjacentScreens] - Отображать ли старую ячейку при анимации перехода на новую ячейку панели.
* @property {boolean} [saveHistoryInPool] - Хранить ли верстку ячеек панели, которые хранятся в истории переходов.
* @property {number} [pointersForSwipe] - Количество пальцев, необходимых для свайпа при переходе на новую ячейку на мобильных устройствах.
* @property {boolean} [isDirectPath] - Использовать ли при поиске кратчайшего пути до указанной модели только переходы
* от потомков к предкам и от предков к потомкам (иначе переходы по массивам предков и по массивам потомков тоже будут считаться отдельными переходами)
* @property {boolean} [savePrevious] - сохранять ли предков (если шаг в сторону потомка) и потомков (если шаг в сторону предка), с которых был сделан переход,
* чтобы в дальнейшем вернутся в них при переходе обратно с ячейки, в которую был сделан переход.
* @property {boolean} [cyclicStep] - делать ли цикличный переход в контексте массива моделей, в котором будет искаться новая модель.
* @property {Moving~getRight} [getRight] - Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход вправо
* @property {Moving~getLeft} [getLeft] - Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход влево
* @property {Moving~getTop} [getTop] - Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход вверх
* @property {Moving~getBottom} [getBottom] - Функция, задающая алгоритм поиска ячейки, в которую должен быть осуществлен переход вниз
*/
/**
* Применить конфигурацию к панели
* @param {Moving~config} config - конфигурация
* @memberOf Moving
*/
Moving.prototype.configure = function(config) {
if (!config) return;
this._plugins.forEach(function(plugin) {
plugin.configure(config);
});
this._animation.configure(config);
this._elementsPool.configure(config);
this._screenManager.configure(config);
this._controlManager.configure(config);
if (config.loadingHtml) {
config.loadingDiv = '<div class="rb__loading">' + config.loadingHtml + '</div>';
} else if (config.loadingHtml === null) {
config.loadingDiv = '';
}
this.beforeMoveDispatcher.configure(config);
this.beforeRenderDispatcher.configure(config);
this.afterRenderDispatcher.configure(config);
this.elementLoadedDispatcher.configure(config);
this.elementUnloadedDispatcher.configure(config);
if (typeof config === 'object') {
if (config.lockControls !== undefined) {
this._lockControls = config.lockControls;
}
}
// updates internal state
if (this._screenManager.getCurScreen()) { // todo то есть если только это не new Moving иначе еще рано да и не надо
this._reloadScreen();
}
};
// todo defineProperty, и вообще доступ к объектам в api сделать через defineProperty
/**
* Получить менеджер управления
* @returns {ControlManager} ControlManager
* @memberOf Moving
*/
Moving.prototype.getControlManager = function() {
return this._controlManager;
};
/**
* Получить менеджер моделей контента
* @returns {ScreenManager} ScreenManager
* @memberOf Moving
*/
Moving.prototype.getScreenManager = function() {
return this._screenManager;
};
/**
* Осуществить переход в указанную сторону.
* @param {string} side - сторона, в которую осуществляется переход
* @param {Boolean} [isSaveHistory=true] - сохранять ли осуществляемый переход в историю переходов
* @returns {Promise|undefined} promise о завершении действия, либо undefined, если переход не требуется
* @memberOf Moving
*/
Moving.prototype.move = function(side, isSaveHistory) {
var self = this,
screen = this._screenManager.getCurScreen();
if (side) {
var nextScreen = self._screenManager.getRelativeScreen(side),
isWrongStep = !nextScreen;
return Promise.race([ this.beforeMoveDispatcher.runActions(
self._moveInner.bind(self, side, screen, isSaveHistory),
[side, self, isWrongStep] // todo может расширить isWrongStep до енама со всеми вариантами которые могут быть
) ]);
}
};
Moving.prototype._moveInner = function(side, screen, isSaveHistory) {
var self = this;
isSaveHistory = isSaveHistory !== false;
if (this._lockControls && !this._locks) {
this._locks = this._controlManager.disableAll();
}
this._screenManager._updateScreens('center', screen, isSaveHistory);
return new Promise(function (moveResolve, moveReject) {
var nextScreen = self._screenManager.getRelativeScreen(side),
curScreen = self._screenManager.getCurScreen();
if (!nextScreen) {
self._animation.goToWrongSide(side).then(function(result) {
if (result) {
self._renderHtml(side, moveResolve.bind(undefined, {
how: 'wrongSide',
isOk: result
}));
}
});
} else if (side === 'center') {
self._elementsPool.prepareSide();
self._animation.goToCenter();
self._renderHtml(side, moveResolve.bind(undefined, {
how: 'center',
isOk: true
}));
} else if (sides.indexOf(side) !== -1) {
self.getScreenManager()._setRelativeScreen(side, nextScreen, curScreen, true, true);
self._screenManager._updateScreens(side, undefined, isSaveHistory);
if (isSaveHistory) {
if (side === 'left' || side === 'right') {
self._screenManager._lastSide = side; // todo надо инкапсулировать
self._screenManager._lastScreen = curScreen;
}
}
self._elementsPool.prepareSide();
self._animation.goToCorrectSide(side).then(function(result) {
if (result) {
self._renderHtml(side, moveResolve.bind(undefined, {
how: 'correctSide',
isOk: result
}));
}
});
} else {
moveReject(new Errors.ArgumentError('side', side));
}
});
};
Moving.prototype._moveByActionValue = function(value, ltrbValues, mapFn) {
function check(value, checkValues) {
var res;
if (Array.isArray(checkValues)) {
res = checkValues.find(function(checkValue) {
return mapFn(value, checkValue);
});
res = res !== undefined;
} else {
res = mapFn(value, checkValues);
}
return res;
}
var side;
if (check(value, ltrbValues[0])) side = 'left';
else if (check(value, ltrbValues[1])) side = 'top';
else if (check(value, ltrbValues[2])) side = 'right';
else if (check(value, ltrbValues[3])) side = 'bottom';
return this.move(side);
};
/**
* Осуществить откат последнего удачного хода, воспользовавшись историей переходов.
* @returns {Promise|undefined} promise о завершении действия, либо undefined, если переход не требуется
* @memberOf Moving
*/
Moving.prototype.moveBack = function() {
var lastStep = this._screenManager.popHistory();
if (lastStep) {
var curScreen = this._screenManager.getCurScreen(),
nextScreen = lastStep.screen,
side = lastStep.side,
mustUpdate = false;
if (side === 'left' && curScreen.getParent(nextScreen)) {
mustUpdate = true;
} else if (side === 'right' && curScreen.getChild(nextScreen)) {
mustUpdate = true;
} else if (side === 'bottom' && this._screenManager._getBottom(curScreen, this._screenManager._cyclicStep) === nextScreen) {
mustUpdate = true;
} else if (side === 'top' && this._screenManager._getTop(curScreen, this._screenManager._cyclicStep) === nextScreen) {
mustUpdate = true;
} else {
return null;
}
if (mustUpdate) {
this.getScreenManager()._setRelativeScreen(side, curScreen, nextScreen);
}
var self = this;
if (side) {
return Promise.race([ this.beforeMoveDispatcher.runActions(
self._moveInner.bind(self, lastStep.side, curScreen, false),
[side, self, null]
) ]);
}
}
return null;
};
/**
* Анимировать запрещенное перемещение в одну из сторон. В панели в эту сторону перемещение может быть запрещено,
* но будет анимировано, будто запрещено.
* @param {string} side - сторона, в которую осуществляется переход
* @returns {Promise} promise о завершении действия
* @memberOf Moving
*/
Moving.prototype.animateWrongSide = function(side) {
return this._animation.goToWrongSide(side);
};
/**
* Установить модель контента в текущую ячейку панели.
* @param {ScreenModel} screen - Устанавливаемая модель контента
* @param {Boolean} [isSaveHistory] - Сохранять ли устанавливаемую модель в историю переходов
* @returns {Promise} promise о завершении установки модели
* @memberOf Moving
*/
Moving.prototype.setScreen = function(screen, isSaveHistory) {
// var self = this;
return this._moveInner('center', screen, isSaveHistory);
// return Promise.race([ this.beforeMoveDispatcher.runActions( // todo как то beforeMoveDispatcher тут не в тему совсем
// self._moveInner.bind(self, 'center', screen, isSaveHistory),
// ['center', self, false]
// ) ]);
};
// todo это слишком много, нужно выделить тот функционал который реально релоадит, и вызывать его везде в том числе в ините где сейчас дергается move
Moving.prototype._reloadScreen = function() {
return this.setScreen(this._screenManager.getCurScreen(), false);
};
/**
* Перезагрузить все панели на странице. Перезагрузка предполагает обновление верстки ячейки панели,
* сброс состояния в верстке. Если в элементах верстки есть подписки, необходимо позаботиться об отписке и
* последующей подписке к новым элементам верстки.
* @prop {string} side - сторона, с которой будет перезагружаться ячейка панели относительно текущей ячейки.
* @memberOf Moving
*/
Moving.prototype.reload = function(side) {
side = side || 'center';
var rbSide = this._elementsPool.getElementBySide(side);
var screen = this._screenManager.getRelativeScreen(side);
rbSide.html(screen.html());
};
Moving.prototype._renderHtml = function(side, moveResolve) {
var self = this,
args = [side, self];
function afterRender() {
self.activate();
self.afterRenderDispatcher.runActions(Utils.nop, args);
if (self._lockControls) {
self._controlManager.enableByValues(self._locks);
self._locks = null;
}
moveResolve();
}
this.beforeRenderDispatcher.runActions(function() {
var iframeCount, loadedIframeCount = 0, iframes;
iframes = self._mainDiv.find('iframe');
self._elementsPool.loadElements();
iframes = self._mainDiv.find('iframe').not(iframes);
iframeCount = iframes.length;
iframes.one('load', function() {
loadedIframeCount++;
if (iframeCount === loadedIframeCount) {
afterRender();
}
});
setTimeout(function() {
if (iframeCount === 0) {
afterRender();
}
}, 0);
}, args);
};
/**
* Активировать панель. Переводит фокус на панель, и, как следствие, панель ловит все события нажатия клавиш.
* @memberOf Moving
*/
Moving.prototype.activate = function() {
this._mainDiv.find('>.rb__fake-element').focus();
};
/**
* Находит кратчайший путь до указанной модели и пошагово переходит от текущей ячейки к той ячейке, в которой располагается найденная модель.
* @param {ScreenModel} toScreen - Искомая модель контента
* @returns {Promise} promise о завершении переходов к намеченной цели
* @memberOf Moving
*/
Moving.prototype.goToScreen = function(toScreen) {
function firstStep(path) {
return new Promise(function(resolve, reject) {
nextStep(path, 0, resolve, reject);
});
}
function nextStep(path, i, resolve, reject) {
if (!path) {
self._controlManager.enableByValues(locks);
reject(new Errors.PathNotFoundError(fromScreen, toScreen));
return;
}
if (i === path.length - 1) {
self._controlManager.enableByValues(locks);
resolve();
return;
}
if ( i > path.length - 1) {
self._controlManager.enableByValues(locks);
reject(new Errors.FatalError('goToScreen : i > path.length - 1'));
return;
}
var curScreen = path[i],
nextScreen = path[i+1],
side;
if (curScreen.getChild(nextScreen)) { // todo почему сначала смотрю на потомков а потом на предков? раньше было наоборот и падало, но от того что я их поменял логика лучше не стала
side = 'right';
} else if (curScreen.getParent(nextScreen)) {
side = 'left';
} else if (self._screenManager._getBottom(curScreen, self._screenManager._cyclicStep) === nextScreen) {
side = 'bottom';
} else if (self._screenManager._getTop(curScreen, self._screenManager._cyclicStep) === nextScreen) {
side = 'top';
} else {
self._controlManager.enableByValues(locks);
reject(new Errors.FatalError('goToScreen : side not found'));
}
self.getScreenManager()._setRelativeScreen(side, curScreen, nextScreen, true);
self.afterRenderDispatcher.add(function() {
self.afterRenderDispatcher.add(function() {
nextStep(path, i+1, resolve, reject);
}, true);
self.move(side);
}, true);
self.setScreen(curScreen, false);
}
var self = this,
locks = this._controlManager.disableAll(),
fromScreen = this._screenManager.getCurScreen(),
path = this._screenManager.findShortestPath(fromScreen, toScreen);
return firstStep(path);
};
/**
* Добавить плагин, расширяющий функционал панели, к общему списку плагинов.
* @param {IPlugin} plugin - добавляемый плагин
* @memberOf Moving
*/
Moving.prototype.addPlugin = function(plugin) {
if (plugin instanceof IPlugin) {
this._plugins.push(plugin);
} else {
console.error('Moving - addPlugin - argument must be IPlugin');
}
};
/**
* Удалить плагин из общего списка плагинов
* @param {IPlugin} plugin - удаляемый плагин
* @memberOf Moving
*/
Moving.prototype.removePlugin = function(plugin) {
var index = this._plugins.indexOf(plugin);
if (index != -1) {
plugin.destroy();
this._plugins.splice(index, 1);
}
};
/**
* Уничтожить панель
* @memberOf Moving
*/
Moving.prototype.destroy = function() {
ScreenModel.unregisterUpdateFn(this._relativeUpdateFn);
this._plugins.forEach(function(plugin) {
plugin.destroy();
});
this.beforeMoveDispatcher.destroy();
this.beforeRenderDispatcher.destroy();
this.afterRenderDispatcher.destroy();
this._animation.destroy();
this._controlManager.destroy();
this._screenManager.destroy();
this._elementsPool.destroy();
this._animation = null;
this._elementsPool = null;
this._screenManager = null;
this._controlManager = null;
this.beforeMoveDispatcher = null;
this.beforeRenderDispatcher = null;
this.afterRenderDispatcher = null;
this._mainDiv.off('click', this._clickHandler);
this._mainDiv.remove();
};
return Moving;
});