В свое время приходилось реализовывать кучу drag and drop'ов под самым разным соусом.
Эта статья представляет собой учебник-выжимку о том, как организовать drag'n'drop в javascript, начиная от основ и заканчивая готовым фреймворком.
Кроме того, почти все javascript-библиотеки реализуют drag and drop так, как написано (в статье дано несколько разных вариантов, не факт что ваш фреймворк использует лучший). Зная, что и как, вы сможете поправить и адаптировать существующую библиотеку под себя.
Drag'n'drop в свое время был замечательным открытием в области интерфейсов, которое позволило упростить большое количество операций.
Одно из самых очевидных применений drag'n'drop - переупорядочение данных. Это могут быть блоки, элементы списка, и вообще - любые DOM-элементы и их наборы.
Перенос мышкой может заменить целую последовательность кликов. И, самое главное, он упрощает внешний вид интерфейса: функции, реализуемые через drag'n'drop, в ином случае потребовали бы дополнительных полей, виджетов и т.п.
Организовать перенос элементов по странице - довольно просто. Для этого нужно:
При помощи события mouseDown отследить клик на переносимом элементе
При каждом движении мыши в обработчике события mouseMove передвигать переносимый элемент по странице.
При отпускании кнопки мыши, то есть наступлении события mouseUp - остановить перенос элемента и произвести все действия, связанные с окончанием drag and drop.
При обработке событий, связанных с мышью, нужен кроссбраузерный способ получения координат курсора из события в обработчике. Кроме того, необходимо знать нажатую кнопку мыши.
Для этого будем использовать свойства which и pageX/pageY, полное описание и механизмы кросс-браузерной реализации которых есть в статье по свойствам объекта событие.
which
кнопка мыши - 1: левая, 2: средняя, 3: правая
pageX/pageY
координаты курсора относительно верхнего-левого угла документа (с учетом прокрутки)
Кроссбраузерно ставить эти свойства на объект будет функция fixEvent (по статье свойства объекта событие):
function fixEvent(e) {
// получить объект событие для IE
e = e || window.event
// добавить pageX/pageY для IE
if ( e.pageX == null && e.clientX != null ) {
var html = document.documentElement
var body = document.body
e.pageX = e.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0)
e.pageY = e.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0)
}
// добавить which для IE
if (!e.which && e.button) {
e.which = e.button & 1 ? 1 : ( e.button & 2 ? 3 : ( e.button & 4 ? 2 : 0 ) )
}
return e
}
В этом коде e.which проходит кросс-браузерную обработку, чтобы корректно отражать нажатую кнопку мыши. Вы можете подробно прочитать об этом в статье Свойства объекта событие.
На демке ниже обработчик mouseMove отслеживает координаты курсора мыши относительно левого-верхнего угла страницы, используя кроссбраузерную обертку fixEvent.
Чтобы начать перенос элемента, мы должны отловить нажатие кнопки мыши на объекте.
Для этого нам пригодится событие mousedown. Повесим обработчик на те элементы, которые хотим сделать доступными для переноса.
Пока этот обработчик будет запоминать объект в глобальной переменной dragObject
element.onmousedown = function(e){
// запомнить переносимый объект
// в переменной dragObject
dragObject = this
// остановить обработку события
return false
}
Остановить обработку события return false очень важно - иначе браузер может запустить свои механизмы перетаскивания элементов и все нам поломать.
В случае с отпусканием кнопки мыши все проще - объект мы уже знаем, так что можно повесить один обработчик onmouseup на document.
При нажатии на элемент он запоминается и выделяется.
Выделение(запоминание) действует на все время, когда нажата кнопка мыши, в том числе при перемещении курсора.
Остается добавить визуальное перемещение элемента - и drag and drop заработает.
Оптимизация onmousedown
Иногда бывает, что объектов, которые могут быть перенесены, много. Например, это ячейка таблицы или длинный список, или дерево статей с массой узлов.
Тогда время инициализации можно сильно сократить, если назначать обработчик onmousedown не на каждый объект переноса, а на контейнер. И уже в самом обработчике по event.target определять, где произошел клик.
Перед дальнейшим развитием проведем реорганизацию кода.
Используем способ описания объекта без new (описан здесь как фабрика объектов), чтобы объявить объект dragMaster, предоставляющий необходимый функционал и отслеживающий перенос.
var dragMaster = (function() {
// private методы и свойства
var dragObject
function mouseDown(e) {
клик на переносимом элементе: начать перенос
}
function mouseMove(e){
if (dragObject) {
отобразить перенос объекта
}
}
function mouseUp(e){
if (dragObject) {
конец переноса
}
}
// public методы и свойства
return {
init: function() {
// инициализовать контроллер
document.onmousemove = mouseMove
document.onmouseup = mouseUp
},
makeDraggable: function(element){
// сделать элемент переносимым
element.onmousedown = mouseDown
}
}
}())
При таком способе задания объекта dragMaster получает публичные свойства (например, makeDraggable), которые имеют доступ к приватным переменным и методам mouse*, dragObject, так как являются вложенными функциями.
Полученный код:
Не загрязняет глобальную область видимости переменными типа dragObject.
Дает единый объект-синглтон dragMaster, управляющий переносом.
Приватные переменные хорошо сжимаются javascript-компрессором, что убыстряет и уменьшает код.
Последний пункт очень важен, так как обработчики mouseMove, mouseUp вызываются при каждом передвижении мыши и поднятии кнопки соответственно. Если mouseMove будет работать медленно, то передвижение курсора станет рваным, заметно тормозным.
На это натыкались многие писатели drag'n'drop приложений. Мы же будем изначально закладывать производительность во все критические участки кода.
Для того, чтобы перенести элемент, ему нужно поставить значение CSS-свойства position в absolute. Тогда он будет позиционироваться относительно верхнего-левого угла документа (точнее говоря, относительно ближайшего родителя, у которого position - relative/absolute, но у нас таких нет), и установка CSS-свойств left и top в координаты курсора мыши поместит левый-верхний угол элемента непосредственно под указатель.
Для перемещения элемента нам достаточно всего-лишь обновлять значения left/top при каждом движении мыши mousemove!
Посетитель обычно кликает не в левый-верхний угол, а куда угодно на элементе.
Поэтому чтобы элемент не прилипал к курсору верхним-левым углом, к позиции элемента необходимо добавить смещение мыши на момент клика.
На рисунке ниже mouseX/mouseY - координаты курсора мыши, а positionX/positionY - координаты верхнего-левого угла элемента, которые легко получить из DOM:
Это изначальное смещение мы запоминаем при клике, прибавляем его при начале движения и сохраняем в дальнейшем.
Тогда позиция элемента относительно курсора мыши будет все время одной и той же.
var dragMaster = (function() {
var dragObject
var mouseOffset
// получить сдвиг target относительно курсора мыши
function getMouseOffset(target, e) {
var docPos = getPosition(target)
return {x:e.pageX - docPos.x, y:e.pageY - docPos.y}
}
function mouseUp(){
dragObject = null
// очистить обработчики, т.к перенос закончен
document.onmousemove = null
document.onmouseup = null
document.ondragstart = null
document.body.onselectstart = null
}
function mouseMove(e){
e = fixEvent(e)
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
return false
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
dragObject = this
// получить сдвиг элемента относительно курсора мыши
mouseOffset = getMouseOffset(this, e)
// эти обработчики отслеживают процесс и окончание переноса
document.onmousemove = mouseMove
document.onmouseup = mouseUp
// отменить перенос и выделение текста при клике на тексте
document.ondragstart = function() { return false }
document.body.onselectstart = function() { return false }
return false
}
return {
makeDraggable: function(element){
element.onmousedown = mouseDown
}
}
}())
function getPosition(e){
var left = 0
var top = 0
while (e.offsetParent){
left += e.offsetLeft
top += e.offsetTop
e = e.offsetParent
}
left += e.offsetLeft
top += e.offsetTop
return {x:left, y:top}
}
В коде появилась новая функция getPosition(элемент) - она получает абсолютные координаты верхнего-правого угла элемента. Функция это стандартная и много где используемая.
Кроме того, при начале переноса останавливается выделение и перенос текста браузером:
Если этого не сделать, то движение курсора мыши при нажатой кнопке будет не только перемещать элемент, но и, например, выделять текст под собой (стандартная функция выделения текста на странице).
Иногда красивее и удобнее - визуально перемещать не сам элемент, а его клон или макет.
Например, переносимый объект очень сложен, и его передвижение целиком тормозит браузер и выглядит громоздко/неэстетично.
Сам элемент при этом скрывается display/visibility='none' или просто остается на месте, в зависимости от логики интерфейса.
Переносимый клон инициализуется в начале переноса и уничтожается в конце.
Когда иконка опущена, нам необходимо определить, куда.
В другое хранилище - переместить. В корзину - удалить, и т.п.
Существенная техническая проблема заключается в том, что событие mouseup сработает не на корзине, а на переносимом элементе, т.к. курсор мыши находится именно над ним.
Поэтому в событии будет информация об элементе непосредственно под курсором. На картинке выше event.target = сердечко, а корзина в объекте события event не присутствует.
Определить, что иконка опущена на корзину, можно, сравнив координаты корзины с коорданатами мыши на момент события.
В момент опускания на корзину выводится сообщение, перемещаемая иконка - в переменной dragObject, цель переноса (корзина, объект-акцептор) - в переменной currentDropTarget.
В коде контроллера: функции, тело которых заменено на "...", остались без изменения с прошлого примера.
var dragMaster = (function() {
var dragObject
var mouseOffset
var dropTargets = []
function mouseUp(e){
e = fixEvent(e)
for(var i=0; i<dropTargets.length; i++){
var targ = dropTargets[i]
var targPos = getPosition(targ)
var targWidth = parseInt(targ.offsetWidth)
var targHeight = parseInt(targ.offsetHeight)
if(
(e.pageX > targPos.x) &&
(e.pageX < (targPos.x + targWidth)) &&
(e.pageY > targPos.y) &&
(e.pageY < (targPos.y + targHeight))){
alert("перенесен объект dragObject на акцептор currentDropTarget")
}
}
dragObject = null
removeDocumentEventHandlers()
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
dragObject = this
mouseOffset = getMouseOffset(this, e)
addDocumentEventHandlers()
return false
}
function removeDocumentEventHandlers() {
document.onmousemove = null
document.onmouseup = null
document.ondragstart = null
document.body.onselectstart = null
}
function addDocumentEventHandlers() {
document.onmousemove = mouseMove
document.onmouseup = mouseUp
document.ondragstart = function() { return false }
document.body.onselectstart = function() { return false }
}
function getMouseOffset(target, e) {...}
function mouseMove(e) {...}
return {
makeDraggable: function(element){...},
addDropTarget: function(dropTarget){
dropTargets.push(dropTarget)
}
}
}())
var dragMaster2 = (function() {
var dragObject
var mouseOffset
var dropTargets = []
function mouseUp(e){
e = fixEvent(e)
for(var i=0; i<dropTargets.length; i++){
var targ = dropTargets[i]
var targPos = getPosition(targ)
var targWidth = parseInt(targ.offsetWidth)
var targHeight = parseInt(targ.offsetHeight)
if(
(e.pageX > targPos.x) &&
(e.pageX < (targPos.x + targWidth)) &&
(e.pageY > targPos.y) &&
(e.pageY < (targPos.y + targHeight))){
alert("dragObject was dropped onto currentDropTarget!")
}
}
dragObject = null
removeDocumentEventHandlers()
}
function removeDocumentEventHandlers() {
document.onmousemove = null
document.onmouseup = null
document.ondragstart = null
document.body.onselectstart = null
}
function getMouseOffset(target, e) {
var docPos = getPosition(target)
return {x:e.pageX - docPos.x, y:e.pageY - docPos.y}
}
function mouseMove(e){
e = fixEvent(e)
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
return false
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
dragObject = this
mouseOffset = getMouseOffset(this, e)
addDocumentEventHandlers()
return false
}
function addDocumentEventHandlers() {
document.onmousemove = mouseMove
document.onmouseup = mouseUp
// отменить перенос и выделение текста при клике на тексте
document.ondragstart = function() { return false }
document.body.onselectstart = function() { return false }
}
return {
makeDraggable: function(element){
element.onmousedown = mouseDown
},
addDropTarget: function(dropTarget){
dropTargets.push(dropTarget)
}
}
}())
function getPosition(e){
var left = 0;
var top = 0;
while (e.offsetParent){
left += e.offsetLeft;
top += e.offsetTop;
e = e.offsetParent;
}
left += e.offsetLeft;
top += e.offsetTop;
return {x:left, y:top};
}
Основных изменений всего три.
Добавлен массив dropTargets и функция addDropTarget, которая добавляет в него элементы, на которые можно дропать.
Измененный обработчик mouseUp теперь проходит в цикле по возможным таким объектам и проверяет, не находится ли курсор внутри ограничивающего объект прямоугольника.
Если да, то демка всего лишь выводит сообщение. Реально приложение, конечно, может сделать более сложные действия.
Кроме того, установка и удаление обработчиков событий для document выделены в отдельные функции - просто в целях лучшей читаемости.
В удобном интерфейсе мы, скорее всего, захотим как-то показывать посетителю, над каким объектом он сейчас находится.
Единственно место, где это можно сделать - обработчик mouseMove. Сама проверка, над чем курсор сейчас находится, полностью аналогична mouseUp.
Однако, так как mouseMove выполняется при каждом передвижении мыши, его надо максимально оптимизировать.
Функция getPosition - довольно медленная: она работает с DOM, и ей надо пройти по всей цепочке offsetParent. Выполнять ее каждый раз при движении мыши для поиска текущего акцептора - все равно что нажать на большой-большой тормоз.
Стандартным выходом в такой ситуации является кеширование координат акцепторов, а точнее - их ограничивающих прямоугольников, так чтобы код mouseMove был максимально прост.
Код уже стал довольно длинным, поэтому в листинге ниже повторяющиеся фрагменты заменены на троеточие "..."
var dragMaster = (function() {
var dragObject
var mouseOffset
var dropTargets = []
/* кеш прямоугольников границ акцепторов */
var dropTargetRectangles
/* текущий акцептор, над которым объект в данный момент */
var currentDropTarget
function cacheDropTargetRectangles() {
dropTargetRectangles = /* сделать кеш прямоугольников */
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
/* начать перенос */
dragObject = this
mouseOffset = getMouseOffset(this, e)
/* закешировать прямоугольники при начале переноса */
cacheDropTargetRectangles()
addDocumentEventHandlers()
return false
}
function getCurrentTarget(e) {
var dropTarget = /* взять из кеша прямоугольник, в котором мышь */
return dropTarget /* null, если мы не над акцепторами */
}
function mouseMove(e){
/* визуально показать перенос объекта */
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
/* newTarget = над каким акцептором сейчас объект */
var newTarget = getCurrentTarget(e)
/* если ушли со старого акцептора */
if (currentDropTarget && currentDropTarget != newTarget) {
/* убрать выделение currentDropTarget */
}
/* пришли на новый акцептор (возможно null) */
currentDropTarget = newTarget
/* если новый акцептор существует (не null) */
if (newTarget) {
/* выделить newTarget */
}
return false;
}
function mouseUp(ev){
if (currentDropTarget) {
alert("перенесен объект dragObject на акцептор currentDropTarget")
/* убрать выделение с currentDropTarget */
}
/* конец операции переноса */
dragObject = null
removeDocumentEventHandlers()
}
function getMouseOffset(target, e) {...}
function addDocumentEventHandlers() {...}
function removeDocumentEventHandlers() {...}
return {
...
}
}())
Полностью рабочий вариант с кешем и т.п. - здесь:
var dragMaster = (function() {
var dragObject
var mouseOffset
var dropTargets = []
var dropTargetRectangles
var currentDropTarget
function cacheDropTargetRectangles() {
dropTargetRectangles = []
for(var i=0; i<dropTargets.length; i++){
var targ = dropTargets[i];
var targPos = getPosition(targ);
var targWidth = parseInt(targ.offsetWidth);
var targHeight = parseInt(targ.offsetHeight);
dropTargetRectangles.push({
xmin: targPos.x,
xmax: targPos.x + targWidth,
ymin: targPos.y,
ymax: targPos.y + targHeight,
dropTarget: targ
})
}
}
function mouseUp(ev){
if (currentDropTarget) {
alert("перенесен объект dragObject на акцептор currentDropTarget")
showRollOff(currentDropTarget)
}
dragObject = null
removeDocumentEventHandlers()
}
function getCurrentTarget(e) {
for(var i=0; i<dropTargetRectangles.length; i++){
var rect = dropTargetRectangles[i];
if(
(e.pageX > rect.xmin) &&
(e.pageX < rect.xmax) &&
(e.pageY > rect.ymin) &&
(e.pageY < rect.ymax)){
return rect.dropTarget
}
}
return null
}
function mouseMove(e){
e = fixEvent(e)
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
var newTarget = getCurrentTarget(e)
if (currentDropTarget && currentDropTarget != newTarget) {
showRollOff(currentDropTarget)
}
currentDropTarget = newTarget
if (newTarget) {
showRollOn(newTarget)
}
return false;
}
function showRollOn(elem) {
elem.className = 'uponMe'
}
function showRollOff(elem) {
elem.className = ''
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
dragObject = this
mouseOffset = getMouseOffset(this, e)
cacheDropTargetRectangles()
addDocumentEventHandlers()
return false
}
function getMouseOffset(target, e) {...}
function addDocumentEventHandlers() {...}
function removeDocumentEventHandlers() {...}
return {
...
}
}())
var dragMaster3 = (function() {
var dragObject
var mouseOffset
var dropTargets = []
var dropTargetRectangles
var currentDropTarget
function cacheDropTargetRectangles() {
dropTargetRectangles = []
for(var i=0; i<dropTargets.length; i++){
var targ = dropTargets[i];
var targPos = getPosition(targ);
var targWidth = parseInt(targ.offsetWidth);
var targHeight = parseInt(targ.offsetHeight);
dropTargetRectangles.push({
xmin: targPos.x,
xmax: targPos.x + targWidth,
ymin: targPos.y,
ymax: targPos.y + targHeight,
dropTarget: targ
})
}
}
function mouseUp(ev){
if (currentDropTarget) {
alert("droped dragObject into curTarget")
showRollOff(currentDropTarget)
}
dragObject = null
removeDocumentEventHandlers()
}
function removeDocumentEventHandlers() {
document.onmousemove = null
document.onmouseup = null
document.ondragstart = null
document.body.onselectstart = null
}
function getMouseOffset(target, e) {
var docPos = getPosition(target)
return {x:e.pageX - docPos.x, y:e.pageY - docPos.y}
}
function getCurrentTarget(e) {
for(var i=0; i<dropTargetRectangles.length; i++){
var rect = dropTargetRectangles[i];
if(
(e.pageX > rect.xmin) &&
(e.pageX < rect.xmax) &&
(e.pageY > rect.ymin) &&
(e.pageY < rect.ymax)){
return rect.dropTarget
}
}
return null
}
function mouseMove(e){
e = fixEvent(e)
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
var newTarget = getCurrentTarget(e)
if (currentDropTarget && currentDropTarget != newTarget) {
showRollOff(currentDropTarget)
}
currentDropTarget = newTarget
if (newTarget) {
showRollOn(newTarget)
}
return false;
}
function showRollOn(elem) {
elem.className = 'uponMe'
}
function showRollOff(elem) {
elem.className = ''
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
dragObject = this
mouseOffset = getMouseOffset(this, e)
cacheDropTargetRectangles()
addDocumentEventHandlers()
return false
}
function addDocumentEventHandlers() {
document.onmousemove = mouseMove
document.onmouseup = mouseUp
// отменить перенос и выделение текста при клике на тексте
document.ondragstart = function() { return false }
document.body.onselectstart = function() { return false }
}
return {
makeDraggable: function(element){
element.onmousedown = mouseDown
},
addDropTarget: function(dropTarget){
dropTargets.push(dropTarget)
}
}
}())
function getPosition(e){
var left = 0;
var top = 0;
while (e.offsetParent){
left += e.offsetLeft;
top += e.offsetTop;
e = e.offsetParent;
}
left += e.offsetLeft;
top += e.offsetTop;
return {x:left, y:top};
}
Следуя общему принципу отделения мух от котлет - лучше отделить простой клик на объекте от начала drag and drop.
Еще одна причина - дорогая инициализация drag & drop: нужно прокешировать все возможные акцепторы. Совершенно не обязательно это делать на mousedown, если имеем простой клик.
Как отделить? Очень просто:
При mousedown запомнить координаты и объект, но пока не начинать перенос
Если произошло событие mouseup - это был всего лишь клик, сбросить координаты
В mousemove проверить: если есть запомненные координаты и курсор отошел от них хотя бы на 2 пикселя - начать перенос
В коде этой демки стоит расстояние не 2, а 25 пикселей, в целях наглядности происходящего.
До перемещения курсора на 25 пикселей вверх или вниз перенос не начнется.
Код демо:
var dragMaster = (function() {
...
var mouseDownAt
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
mouseDownAt = { x: e.pageX, y: e.pageY, dragObject: this }
addDocumentEventHandlers()
return false
}
function mouseMove(e){
e = fixEvent(e)
if (mouseDownAt) {
if (Math.abs(mouseDownAt.x-e.pageX)<25 && Math.abs(mouseDownAt.y-e.pageY)<25) {
// слишком близко, возможно это клик
return
}
// курсор нажатой мыши отвели далеко - начинаем перенос
dragObject = mouseDownAt.dragObject
mouseOffset = getMouseOffset(dragObject, mouseDownAt.x, mouseDownAt.y)
cacheDropTargetRectangles()
// запомненные координаты нам больше не нужны
mouseDownAt = null
}
showDrag(e) // показать перенос
return false;
}
function mouseUp(ev){
if (!dragObject) {
// ничего не начали нести, был просто клик
mouseDownAt = null
} else {
// чего-то несем - обрабатываем конец переноса
if (currentDropTarget) {
alert("перенесен объект dragObject на акцептор currentDropTarget")
showRollOff(currentDropTarget)
}
dragObject = null
}
// (возможный) drag and drop завершен
removeDocumentEventHandlers()
}
function showDrag(e) {
// перенести объект
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
// подсветить акцептор
var newTarget = getCurrentTarget(e)
if (currentDropTarget && currentDropTarget != newTarget) {
showRollOff(currentDropTarget)
}
currentDropTarget = newTarget
if (newTarget) {
showRollOn(newTarget)
}
}
function getMouseOffset(target, x, y) {
// для удобства поменяли синтаксис: x/y вместо event
var docPos = getPosition(target)
return {x:x - docPos.x, y:y - docPos.y}
}
function cacheDropTargetRectangles() {...}
function removeDocumentEventHandlers() {...}
function getCurrentTarget(e) {...}
function showRollOn(elem) {...}
function showRollOff(elem) {...}
function addDocumentEventHandlers() {...}
return {
...
}
}())
var dropMove2 = (function() {
var dragObject
var mouseOffset
var mouseDownAt
var dropTargets = []
var dropTargetRectangles
var currentDropTarget
function cacheDropTargetRectangles() {
dropTargetRectangles = []
for(var i=0; i<dropTargets.length; i++){
var targ = dropTargets[i];
var targPos = getPosition(targ);
var targWidth = parseInt(targ.offsetWidth);
var targHeight = parseInt(targ.offsetHeight);
dropTargetRectangles.push({
xmin: targPos.x,
xmax: targPos.x + targWidth,
ymin: targPos.y,
ymax: targPos.y + targHeight,
dropTarget: targ
})
}
}
function mouseUp(ev){
if (!dragObject) {
mouseDownAt = null
} else {
if (currentDropTarget) {
alert("перенесен объект dragObject на акцептор currentDropTarget")
showRollOff(currentDropTarget)
}
dragObject = null
}
removeDocumentEventHandlers()
}
function removeDocumentEventHandlers() {
document.onmousemove = null
document.onmouseup = null
document.ondragstart = null
document.body.onselectstart = null
}
function getMouseOffset(target, x, y) {
var docPos = getPosition(target)
return {x:x - docPos.x, y:y - docPos.y}
}
function getCurrentTarget(e) {
for(var i=0; i<dropTargetRectangles.length; i++){
var rect = dropTargetRectangles[i];
if(
(e.pageX > rect.xmin) &&
(e.pageX < rect.xmax) &&
(e.pageY > rect.ymin) &&
(e.pageY < rect.ymax)){
return rect.dropTarget
}
}
return null
}
function mouseMove(e){
e = fixEvent(e)
if (mouseDownAt) {
if (Math.abs(mouseDownAt.x-e.pageX)<25 && Math.abs(mouseDownAt.y-e.pageY)<25) {
return
}
dragObject = mouseDownAt.dragObject
mouseOffset = getMouseOffset(dragObject, mouseDownAt.x, mouseDownAt.y)
cacheDropTargetRectangles()
mouseDownAt = null
}
showDrag(e)
return false;
}
function showDrag(e) {
with(dragObject.style) {
position = 'absolute'
top = e.pageY - mouseOffset.y + 'px'
left = e.pageX - mouseOffset.x + 'px'
}
var newTarget = getCurrentTarget(e)
if (currentDropTarget && currentDropTarget != newTarget) {
showRollOff(currentDropTarget)
}
currentDropTarget = newTarget
if (newTarget) {
showRollOn(newTarget)
}
}
function showRollOn(elem) {
elem.className = 'uponMe'
}
function showRollOff(elem) {
elem.className = ''
}
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
mouseDownAt = { x: e.pageX, y: e.pageY, dragObject: this }
addDocumentEventHandlers()
return false
}
function addDocumentEventHandlers() {
document.onmousemove = mouseMove
document.onmouseup = mouseUp
// отменить перенос и выделение текста при клике на тексте
document.ondragstart = function() { return false }
document.body.onselectstart = function() { return false }
}
return {
makeDraggable: function(element){
element.onmousedown = mouseDown
},
addDropTarget: function(dropTarget){
dropTargets.push(dropTarget)
}
}
}())
function getPosition(e){
var left = 0;
var top = 0;
while (e.offsetParent){
left += e.offsetLeft;
top += e.offsetTop;
e = e.offsetParent;
}
left += e.offsetLeft;
top += e.offsetTop;
return {x:left, y:top};
}
Бывает, что возможных акцепторов очень много. Тогда код, кеширующий прямоугольники при начале переноса, будет тормозить.
Визуально это проявляется как задержка от клика на объекте до его фактического переноса - потому что долго, с 100% поеданием одного ядра CPU обрабатывается mousedown.
Речь тут идет о 100 акцепторах или больше - например, при переносе между длинными списками или деревьями. Хотя какие-то тормоза могут быть заметны и от 50.
Этот малоизвестный метод работает во всех браузерах и возвращает элемент по координатам на странице.
Firefox/IE используют для этого clientX/Y, а Opera, Chrome и Safari - pageX/Y.
Возвращенный элемент является самым глубоко вложенным на точке с координатами (x,y). Это может быть текстовый узел в том числе.
Следуя по цепочке родителей, легко найти нужного акцептора.
На время вызова elementFromPoint необходимо спрятать переносимый элемент, чтобы он не закрывал акцептора.
Псевдокод будет выглядеть так:
function getCurrentTarget(e) {
dragObject.style.display = 'none' // спрятать
if (navigator.userAgent.match('MSIE') || navigator.userAgent.match('Gecko')) {
// IE || FF
var elem = document.elementFromPoint(e.clientX,e.clientY)
} else {
var elem = document.elementFromPoint(e.pageX,e.pageY)
}
dragObject.style.display = '' // показать побыстрее
while (elem!= null) {
if (elem является акцептором) {
return elem
}
elem = elem.parentNode
}
// не нашли
return null
}
И мы знаем, что каждый элемент имеет фиксированную высоту и отступы:
.articles-list li {
height: 20px;
margin: 0px;
padding: 0px;
}
В таком случае, можно вычислить номер LI, разделив общую высоту контейнера на высоту акцептора.
function getCurrentTarget(e) {
var rect = /* ограничивающий прямоугольник для UL */
// (ulX, ulY) - координаты относительно верхнего-левого угла контейнера
var ulX= e.pageX - rect.xmin
var ulY = e.pageY - rect.ymin
if ( ulX < 0 || ulX > rect.xmax-rect.xmin || ulY < 0 || ulY > rect.ymax-rect.ymin) {
/* событие за границами списка */
return null
}
/* по 20px на каждого ребенка-акцептора */
var childNum = ulY / 20
return rect.dropTarget.childNodes[childNum]
}
Конечно, такая оптимизация возможна не всегда и зависит от конкретной задачи.
В ряде задач допустимо не сохранять переносимый элемент под курсором, а передвинуть его на несколько пикселей в сторону, то mouseUp и mouseMove будут срабатывать уже на акцепторе, который станет возможным получить из event.target.
Да, получится не так красиво, но это может быть единственным выходом.
Объект DragObject - обобщенный переносимый объект, который привязывается к DOM-элементу. Он представлен на следующем листинге.
function DragObject(element) {
element.dragObject = this
dragMaster.makeDraggable(element)
var rememberPosition
var mouseOffset
this.onDragStart = function(offset) {
var s = element.style
rememberPosition = {top: s.top, left: s.left, position: s.position}
s.position = 'absolute'
mouseOffset = offset
}
this.hide = function() {
element.style.display = 'none'
}
this.show = function() {
element.style.display = ''
}
this.onDragMove = function(x, y) {
element.style.top = y - mouseOffset.y +'px'
element.style.left = x - mouseOffset.x +'px'
}
this.onDragSuccess = function(dropTarget) { }
this.onDragFail = function() {
var s = element.style
s.top = rememberPosition.top
s.left = rememberPosition.left
s.position = rememberPosition.position
}
this.toString = function() {
return element.id
}
}
Использование:
new DragObject(element)
Достаточно одного вызова new, т.к функция DragObject добавляет конструируемый объект к element в свойство element.dragObject:
...
element.dragObject = this
...
Методы:
onDragStart(offset)
Вызывается при начале переноса. В текущей реализации отрывает объект "от земли" и запоминает текущую позицию в rememberPosition и сдвиг курсора мыши от левого-верхнего угла объекта в mouseOffset.
В другой реализации может показывать перенос как-то по-другому, например создавать "переносимый клон" объекта.
onDragMove(x, y)
Вызывается при переносе объекта в координаты (x,y). Отображает перенос.
onDragFail()
Обрабатывает неудачный перенос. В текущей реализации возвращает объект на старые координаты.
Вообще говоря, можно делать это с анимацией, показывая как объект "перелетает" на старое место.
onDragSuccess(dropTarget)
Обрабатывает успешный перенос. В текущей реализации обработка успешного переноса целиком сосредоточена у принимающего объекта DropTaget, поэтому эта функция пустая.
show/hide()
Показать/спрятать переносимый объект - вспомогательные методы
Методов у DropTarget поменьше, чем у DragObject. Еще бы, акцептору ведь не нужно анимировать собственный перенос.
canAccept(dragObject)
При проносе объекта над DropTarget, dragMaster спросит у акцептора, может ли он принять dragObject. Если нет - dragMaster проигнорирует этот акцептор.
В текущей реализации всегда возвращает true, то есть положить можно. Вообще говоря, может проверять класс переносимого объекта:
this.canAccept = function(dragObject) {
// могу принять только объекты типа TreeNodeDragObject
return dragObject instanceof TreeNodeDragObject
}
accept(dragObject)
Принимает переносимый объект. Объект может быть перемещен(в другой каталог) или уничтожен(корзина) - зависит от вашей логики обработки переноса.
В конкретном приложении стоит посмотреть особо, какую часть логики конца переноса стоит поместить DragObject#onDragSuccess, а какую - в DropTarget#accept. Как правило, основную логику переноса удобно сосредоточить у DropTarget.
onLeave/onEnter
Анимация возможности положить объект на акцептор. Как правило, акцептор при этом подсвечивается. Эти методы будут вызваны только если акцептор может принять (canAccept) переносимый объект.
Ну и, наконец, похудевший и лишенный большинства обязанностей dragMaster.
var dragMaster = (function() {
var dragObject
var mouseDownAt
var currentDropTarget
function mouseDown(e) {
e = fixEvent(e)
if (e.which!=1) return
mouseDownAt = { x: e.pageX, y: e.pageY, element: this }
addDocumentEventHandlers()
return false
}
function mouseMove(e){
e = fixEvent(e)
// (1)
if (mouseDownAt) {
if (Math.abs(mouseDownAt.x-e.pageX)<5 && Math.abs(mouseDownAt.y-e.pageY)<5) {
return false
}
// Начать перенос
var elem = mouseDownAt.element
// текущий объект для переноса
dragObject = elem.dragObject
// запомнить, с каких относительных координат начался перенос
var mouseOffset = getMouseOffset(elem, mouseDownAt.x, mouseDownAt.y)
mouseDownAt = null // запомненное значение больше не нужно, сдвиг уже вычислен
dragObject.onDragStart(mouseOffset) // начали
}
// (2)
dragObject.onDragMove(e.pageX, e.pageY)
// (3)
var newTarget = getCurrentTarget(e)
// (4)
if (currentDropTarget != newTarget) {
if (currentDropTarget) {
currentDropTarget.onLeave()
}
if (newTarget) {
newTarget.onEnter()
}
currentDropTarget = newTarget
}
// (5)
return false
}
function mouseUp(){
if (!dragObject) { // (1)
mouseDownAt = null
} else {
// (2)
if (currentDropTarget) {
currentDropTarget.accept(dragObject)
dragObject.onDragSuccess(currentDropTarget)
} else {
dragObject.onDragFail()
}
dragObject = null
}
// (3)
removeDocumentEventHandlers()
}
function getMouseOffset(target, x, y) {
var docPos = getOffset(target)
return {x:x - docPos.left, y:y - docPos.top}
}
function getCurrentTarget(e) {
// спрятать объект, получить элемент под ним - и тут же показать опять
if (navigator.userAgent.match('MSIE') || navigator.userAgent.match('Gecko')) {
var x=e.clientX, y=e.clientY
} else {
var x=e.pageX, y=e.pageY
}
// чтобы не было заметно мигание - максимально снизим время от hide до show
dragObject.hide()
var elem = document.elementFromPoint(x,y)
dragObject.show()
// найти самую вложенную dropTarget
while (elem) {
// которая может принять dragObject
if (elem.dropTarget && elem.dropTarget.canAccept(dragObject)) {
return elem.dropTarget
}
elem = elem.parentNode
}
// dropTarget не нашли
return null
}
function addDocumentEventHandlers() {
document.onmousemove = mouseMove
document.onmouseup = mouseUp
document.ondragstart = document.body.onselectstart = function() {return false}
}
function removeDocumentEventHandlers() {
document.onmousemove = document.onmouseup = document.ondragstart = document.body.onselectstart = null
}
return {
makeDraggable: function(element){
element.onmousedown = mouseDown
}
}
}())
mouseDown(e)
Метод не изменился. Он запоминает позицию, на которой произошло нажатие кнопки мыши mouseDownAt и добавляет остальные обработчики слежения за переносом addDocumentEventHandlers().
mouseMove(e)
Этот обработчик присоединяется к document в момент mouseDown.
Если в момент его срабатывания есть запомненное нажатие и курсор отошел больше чем на 5 пикселей - значит начался перенос. DragObject'у это сообщается вызовом onDragStart с передачей текущего сдвига курсора относительно левого-верхнего угла mouseOffset.
Объект информируется о текущих координатах переноса.
Вычисляем текущий акцептор при помощи модифицированного метода getCurrentTarget (см. выше в разделе "Оптимизация").
Обработать нового акцептора и уход со старого акцептора.
Вернуть false для блокирования действий браузера и всплывания события mouseMove
mouseUp()
Этот обработчик завершает (возможный) перенос. Обратите внимание - само событие в данном случае не нужно. Весь процесс переноса отслеживается событием mouseMove.
Если объект переноса не установлен, значит перед этим было простое нажатие mouseDown, элемент не отнесли на 5 пикселей в сторону - это не drag'n'drop.
При наличии акцептора - перенос успешно завершается, если акцептора нет - отменяется.
В любом случае в конце все обработчики с документа снимаются
Функция определения позиции элемента getPosition заменена на более точный вариант getOffset, описанный в статье про определение координат.
В процессе переноса акцепторы объекты могут сдвигаться, освобождая место.
Если вы используете кеш координат акцепторов, то при этом производится соответствующее обновление кеша, но вместо полного перевычисления обновляются только координаты сдвинувшихся объектов.
Например, при раздвижении списка вниз - увеличиваются Y-координаты всех сдвинувшихся LI.
При этом для удобного обновления кеш делается не массивом, а объектом с доступом по ID элемента, так чтобы можно было легко обновить именно нужные координаты.
Иногда, например, при смене позиции элемента в списке, объект переносится не на акцептор, а между акцепторами. Как правило, "между" - имеется в виду по высоте.
Для этого логику определения currentDropTarget нужно поменять. Возможно два варианта:
Допустим перенос как между, так и над
В этом случае акцептор делится на 3 части по высоте clientHeight: 25% - 50% - 25%, и определяется попадание координаты события на нужную часть.
Перенос только между
Акцептор делится на две части: 50% - 50%
Кроме того, в дополнение к текущему акцептору currentDropTarget добавляется флаг, обозначающий, куда относительно акцептора происходит перенос.
Индикацию "переноса между" удобнее всего делать либо раздвижением элементов, либо показом полосы-индикатора border-top/border-bottom, как показано на рисунке ниже:
Обычно при переносе объекта куда-либо посетитель может просто отпустить его в любом месте.
При этом drag and drop фреймворк анимирует отмену переноса. Один из частых вариантов - скольжение объекта обратно к исходному месту, откуда его взяли.
Конечно, для этого исходное место необходимо запомнить.
Если перетаскивается аватарка(клон), то можно его и просто уничтожить.
Совершенно не факт, что любой объект можно перенести на любой аксептор.
Как правило, все с точностью наоборот.
Акцептор может быть недоступен по двум причинам:
либо это несовпадение типов, для этого drag and drop должен предусматривать различные типы объектов/акцепторов и проверку их соответствия
либо у посетителя недостаточно для этого прав, в рамках наложенных CMS ограничений
При переносе над недоступным акцептором - getCurrentTarget просто возвращает null.
Иногда проверку прав и результата переноса необходимо делать на сервере. Как правило, такую проверку выполняют только при mouseUp, чтобы не нагружать сервер излишними запросами во время mouseMove.
Здесь используется два подхода
Синхронный XmlHttpRequest
Запрос отправляется синхронно, чтобы не нарушать общий поток выполнения. Все хорошо, вот только браузер видимо подвисает при отпускании кнопки мыши. Да и другие недостатки у синхронного запроса есть.
Отложенная отмена переноса
Более продвинутый и удобный вариант. Основан на "оптимистичном" сценарии, по которому перенос, как правило, проходит успешно.
Посетитель кладет объект туда, куда он хочет.
Фреймворк перемещает объект, и затем "в фоне" делает асинхронный запрос к серверу. На запрос вешается callback, содержащий информацию, откуда был перенесен объект.
Сервер обрабатывает перенос и возвращает ответ, все ли в порядке.
Если нет - callback выводит ошибку и объект возвращается туда, откуда был перенесен.
Thank you for creating a blog that not only informs but also motivates. Your positivity and поздравление с днем рождения enthusiasm are contagious. Your words have the power to uplift and inspire readers like me. Please keep spreading your light through your incredible blog!
Thanks for an interesting blog. What else may I get that sort of info written in such a perfect approach? I have an undertaking that I am just now operating on, and I have been on the lookout for such info.window replacement cost phoenix
It is ideal to read blogs like this. Have you ever wondered about the benefits of using Shower Toga? No worries if you haven't! It enhances your outdoor experience while promoting ethical and ecological practices like reducing water waste and offering an eco-conscious option. Experience a seamless, rejuvenating, and environmentally responsible outdoor shower solution. Get more details in the article.
Thanks for sharing such an informative post. Do you know guys how to view someone's private Instagram profiles? If you don't know, Don't worry. Now, you can view private Instagram profiles using the Instagram Private Profile Viewer free tool. More information about the tool is provided in the article. Visit the article and get more information about it.
Big Brother Titans is the joint South African and Nigerian edition of the Big Brother franchise. The theme for the first season was "Ziyakhala Wahala".
Everyone is well aware of the importance of safety when
it comes to online sports betting. My website has a lot of
useful sports news information, check it out now. 토토사이트 안전성
يتميز فلتر مياه كوجين بفعاليته في تنقية المياه ب سعر فلتر كوجين المناسب، وهو ما يجعله الخيار المثالي لكل الفئات. ومن خلال ، يمكن الحصول على فلتر مياه كوجين 7 مراحل تايواني بسعر مناسب في عام 2023. بالإضافة إلى ذلك، يتميز هذا الفلتر بالهاوسنجات القوية والمضخة الفعالة، بالإضافة إلى أنه قابل للصيانة على المدى الطويل. كما يتضمن هذا الفلتر الشمعات القادرة على تنقية المياه بشكل مثالي، مما يجعله الخيار الأمثل لمن يبحثون عن جودة عالية بأسعار مناسبة. ويمكن الحصول على فلتر كوجين 7 مراحل تايواني عبر بسعر مناسب وبضمان الوكيل.3400 جنيه
Filme si Seriale Online cu Subtitrare in Romana - FS Online Filme online HD si seriale online HD cu subtitrare in romana pe fsonlinehd
================================================================
Filme si Seriale Online cu Subtitrare in Romana - FS Online Filme online HD si seriale online HD cu subtitrare in romana pe fsonlinehd
================================================================
Pale skin was considered fashionable for both men and women. Lead-based cosmetics were used to achieve this look, which unfortunately had harmful health effects.
Glad to chat your blog, I seem to be forward to more reliable articles and I think we all wish to thank so many good articles, blog to share with us. Suika
Helpful information. Fortunate me I found your site unintentionally, and I am shocked why this coincidence did not took place earlier! I bookmarked it. ufabet168
I really appreciate this wonderful post that you have provided for us. I assure this would be beneficial for most of the people. ทางเข้า biobet" title="ทางเข้า biobet">ทางเข้า biobet
Hello. Cool article. There’s an issue with the website and you might want to test this… The browser is the marketplace chief and a large element of other folks will miss your great writing due to this problem. betflix vip
فلتر فلوكستك 7 مراحل فخر الصناعة التايواني
يتميز فلتر فلوكستك بالكفاءة والجوده العالمية
فهو يعمل بنظام التناضح العكسي RO SYSTEM
ويتميز فلتر فلوكس تك بجودة خاماته واحتوائه على شمعة الكالاين التي ترفع قلوية المياه وخزان تايواني أصلي ٣ طبقات ١٢ لتر وحنفية من الاستانلس ستيل الخالص غير القابل للصدا وشمعات من كربون جوز الهند ، كما أن موتور الفلتر وكل شمعة من شمعات عليها باركود دولي مرتبط بمصنع فلوكستك مباشرة ، ويمكنك فحص الباركود من خلال موبايلك والتأكد من أنه أصلي .
افضل الخصومات لتكييف تكييف يونيون اير 2023 استمتع الان مع شركة تكييف يونيون اير بافضل الخصوصات الجديده على كل الاجهزه التى توجد لدينا ، يعنى هتقدر تشترى الجهاز اللى نفسك فيه وباقل الاسعار اللى مش هتلاقيها غير بس عندنا . تمتعنا شركة تكييف يونيون اير بافضل العروض على الاجهزه المكيفه لانها تريد دائما توفير ما يرضى العميل وليس المال وده ما يجعلنا دائما الشركة الاولى فى مصر وخارجها . الان هتقدر تحصل على تكييف يونيون اير من اقرب فرع لكم لاننا بنوفر لكم فروع لنا فى جميع المحافظات يعنى هيكون متوافر فى كل مكان ، وهتحصل على كل العروض والخصومات التى نوفرها لكم من اى فرع معتمد لنا . خلى صيفك مميزه مع تكييفات يونيون اير وستكون دائما مستمتع باوقاتك مع اسرتك .
فلاتر مياه كوجين: الحل الأمثل لتنقية وتحسين جودة المياه
يعد فلتر كوجين من الخيارات الرائدة للأسر والمؤسسات لتحسين جودة المياه التي يتناولونها يومياً. تجمع هذه الفلاتر بين التكنولوجيا المبتكرة والتصميم المتقدم لتقديم مياه صحية ونقية للشرب والاستخدام المنزلي.
تحليل سعر فلتر مياه عمومى
مواصفات فلتر مياه عمومى
جهاز شمعه
وحده تزيل الشواب والاتربه العلقه بالمياه
فلتر مياة مرحلة واحدة 20 بوصة مع شمعة بولى صنع فى تيوانى لتنقية مياة المنزل بالكامل ويتم تركيبة على الموتور او على الخزان الرئيسي للفيلا او العمارة او غسلات الملابس او الاطباق للحفاظ عليها والاطاله عمر الغسلات ولذلك لمنع الشواب والرمل
Thanks for your personal marvelous posting! I quite enjoyed reading it, you will be a great author. I will always bookmark your blog and will come back down the road. I want to encourage one to continue your great posts, have a nice morning! ufabet168
Raih Keuntungan Besama Cucukakek89 Situs Slot Online Gacor yang menyediakan Bonus Mingguan Setiap hari Selesa Sebagai bentuk appresiasi untuk member aktif yang sudah bermain di cucukakek89. Kunjungi Dan Mainkan di Website Cucukakek89 Terpercaya!!. Mainkan Segera Permainan dengan pilihan Game Terlengkap dengan minimal deposit 25rb.
Does your blog have a contact page? I’m having problems locating it but, I’d like to shoot you an email. I’ve got some ideas for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it develop over time.biogame
Does your blog have a contact page? I’m having problems locating it but, I’d like to shoot you an email. I’ve got some ideas for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it develop over time.biogame
Impressive web site, Distinguished feedback that I can tackle. Im moving forward and may apply to my current job as a pet sitter, which is very enjoyable, but I need to additional expand. ยูฟ่าสล็อต
I’m not that much of a online reader to be honest but your blogs really nice, keep it up! I’ll go ahead and bookmark your site to come back later. All the best betflik vip
Great post! I am actually getting ready to across this information, is very helpful my friend. I also like to recommend a site that is all about buy telegram members. Keep up the good work you are doing here. betflix vip
Thank you for another excellent article. Where else could anybody get that kind oof information in such an ideall means of writing? I’ve a presentation next week, and I’m at the look for such info.ทางเข้า betflix
I think your blog might be having browser compatibility issues. When I look at your website in Ie, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, terrific blog!betflixvip
A debt of gratitude is in order for sharing the information, keep doing awesome... I truly delighted in investigating your site. great asset... Infinite Craft
Hey what a brilliant post I have come across and believe me I have been searching out for this similar kind of post for past a week and hardly came across this. Thank you very much and will look for more postings from you. 中文補習
Thank you for another excellent article. Where else could anybody get that kind oof information in such an ideall means of writing? I’ve a presentation next week, and I’m at the look for such info. ufabet เว็บแม่
Unprecedented blog. I enjoyed investigating your articles. This is to a great degree an awesome investigated for me. I have bookmarked it and I am suspecting examining new articles.brisbane pest control
Unprecedented blog. I enjoyed investigating your articles. This is to a great degree an awesome investigated for me. I have bookmarked it and I am suspecting examining new articles. brisbane pest control
Your insightful exploration truly encapsulates the essence of the topic. I appreciate how you've skillfully woven together various perspectives, offering a comprehensive understanding. Your engaging writing style effortlessly draws readers in, making complex concepts accessible. Allow me to share some information with you Planning your Turkey adventure from Egypt? Navigate the Turkey Visa from Egypt process smoothly with our comprehensive guide. Ensure a hassle-free journey with expert tips and essential information.
THE ONE CARGO คาร์โก้ จีนคุณภาพ ที่ผู้ประกอบการไว้ใจ บริการนำเข้าสินค้าจากจีนมาไทย ส่งเร็วได้มาตรฐาน เปิดบริการทุกวัน นำเข้าสินค้าจากจีน เอกสารครบถ้วนถูกต้อง ใบขน ใบกำกับ ใบ Form E ขนส่งทางรถ ระยะเวลา 3-5 วัน
Drag and drop functionality simplifies the installation of the Shadow Fight 2 Mod APK. Simply download the APK file, drag it to your Android device, and drop it into the appropriate folder. This mod offers unlimited coins, gems, and other features, enhancing your gameplay experience with powerful weapons and unlocked levels.
Explore why Gulaal is among the best clothing brands for women. Our curated collections offer timeless designs and superior quality for every occasion.
Gulaal offers a stunning selection of unstitched suits. Craft your ideal look with our luxurious fabrics and intricate designs for a truly bespoke fashion experience.
Get Direct admission in Symbiosis Institute of Business Management (SIBM) Pune is possible through the management quota. Students interested in the MBA program must meet basic eligibility criteria. Although the fees are higher, direct admission allows students to bypass competitive entrance exams like SNAP for guaranteed entry.
SLS Pune
Get Direct Admission in Symbiosis Law School (SLS) Pune through management quota, ideal for students seeking entry without appearing for competitive exams like SLAT. Eligibility typically includes minimum academic qualifications, and the direct admission process requires consultation with the admissions office to understand available seats and fee structure.
SIT Pune
Get Direct admission at Symbiosis Institute of Technology (SIT) Pune is available for engineering aspirants who do not wish to take entrance exams like JEE. Admissions are offered through management quota, ensuring that eligible students can still secure a seat while paying higher tuition fees than regular admissions.
MIT WPU Pune
Get Direct Admission in MIT World Peace University (MIT WPU) Pune offers direct admission to various undergraduate and postgraduate courses. Students who missed the entrance exam deadlines can apply through the management quota. Eligibility criteria, like minimum percentage in qualifying exams, must be met, and fees for direct admissions tend to be higher.
DY Patil College Pune
Get Direct Admission in DY Patil College Pune provides direct admission through management quota for students across engineering, medical, and management programs. Students can skip competitive exams but must meet academic criteria. This route offers a quicker admission process, though the fee structure for these seats is generally on the higher side.
Sinhgad College Pune
Get Direct admission at Sinhgad College Pune is available via the management quota for undergraduate and postgraduate programs. Students who meet the minimum academic requirements can apply without appearing for entrance exams, though the fees for such admissions are typically higher than the standard admission process.
Amity University Noida
Get Direct Admission in Amity University Noida allows direct admission to its wide range of undergraduate and postgraduate programs through management quota. Students can avoid entrance exams like Amity-JEE or CAT, provided they meet basic eligibility criteria. The fee structure is higher, and seats are limited in popular programs.
Fergusson College Pune
Get Direct Admission in Fergusson College Pune offers limited direct admission options through its management quota. Students need to meet the minimum eligibility criteria, and this route allows them to skip competitive exams. Though direct admission ensures a seat, it comes with a higher fee structure and limited availability.
Bharati Vidyapeeth Pune
Get Direct admission at Bharati Vidyapeeth Pune is available in several undergraduate and postgraduate programs through management quota. Students can bypass entrance exams but need to meet academic requirements. This method ensures a reserved seat in the college, though it often involves higher fees than merit-based seats.
Symbiosis Pune
Get Direct Admission in Symbiosis University Pune offers direct admission through the management quota in many of its constituent colleges like SIBM, SIT, and SLS. Students must fulfill basic academic criteria, and this option allows them to avoid entrance exams like SNAP or SLAT, though the fees are significantly higher.
Get Direct admission in Symbiosis Institute of Business Management (SIBM) Pune is possible through the management quota. Students interested in the MBA program must meet basic eligibility criteria. Although the fees are higher, direct admission allows students to bypass competitive entrance exams like SNAP for guaranteed entry.
SLS Pune
Get Direct Admission in Symbiosis Law School (SLS) Pune through management quota, ideal for students seeking entry without appearing for competitive exams like SLAT. Eligibility typically includes minimum academic qualifications, and the direct admission process requires consultation with the admissions office to understand available seats and fee structure.
SIT Pune
Get Direct admission at Symbiosis Institute of Technology (SIT) Pune is available for engineering aspirants who do not wish to take entrance exams like JEE. Admissions are offered through management quota, ensuring that eligible students can still secure a seat while paying higher tuition fees than regular admissions.
MIT WPU Pune
Get Direct Admission in MIT World Peace University (MIT WPU) Pune offers direct admission to various undergraduate and postgraduate courses. Students who missed the entrance exam deadlines can apply through the management quota. Eligibility criteria, like minimum percentage in qualifying exams, must be met, and fees for direct admissions tend to be higher.
DY Patil College Pune
Get Direct Admission in DY Patil College Pune provides direct admission through management quota for students across engineering, medical, and management programs. Students can skip competitive exams but must meet academic criteria. This route offers a quicker admission process, though the fee structure for these seats is generally on the higher side.
Sinhgad College Pune
Get Direct admission at Sinhgad College Pune is available via the management quota for undergraduate and postgraduate programs. Students who meet the minimum academic requirements can apply without appearing for entrance exams, though the fees for such admissions are typically higher than the standard admission process.
Amity University Noida
Get Direct Admission in Amity University Noida allows direct admission to its wide range of undergraduate and postgraduate programs through management quota. Students can avoid entrance exams like Amity-JEE or CAT, provided they meet basic eligibility criteria. The fee structure is higher, and seats are limited in popular programs.
Fergusson College Pune
Get Direct Admission in Fergusson College Pune offers limited direct admission options through its management quota. Students need to meet the minimum eligibility criteria, and this route allows them to skip competitive exams. Though direct admission ensures a seat, it comes with a higher fee structure and limited availability.
Bharati Vidyapeeth Pune
Get Direct admission at Bharati Vidyapeeth Pune is available in several undergraduate and postgraduate programs through management quota. Students can bypass entrance exams but need to meet academic requirements. This method ensures a reserved seat in the college, though it often involves higher fees than merit-based seats.
Symbiosis Pune
Get Direct Admission in Symbiosis University Pune offers direct admission through the management quota in many of its constituent colleges like SIBM, SIT, and SLS. Students must fulfill basic academic criteria, and this option allows them to avoid entrance exams like SNAP or SLAT, though the fees are significantly higher.
При обработке событий, связанных с мышью, нужен кроссбраузерный способ получения координат курсора из события в обработчике. Кроме того, необходимо знать нажатую кнопку мыши.
SIBM Pune
Get Direct admission in Symbiosis Institute of Business Management (SIBM) Pune is possible through the management quota. Students interested in the MBA program must meet basic eligibility criteria. Although the fees are higher, direct admission allows students to bypass competitive entrance exams like SNAP for guaranteed entry.
SLS Pune
Get Direct Admission in Symbiosis Law School (SLS) Pune through management quota, ideal for students seeking entry without appearing for competitive exams like SLAT. Eligibility typically includes minimum academic qualifications, and the direct admission process requires consultation with the admissions office to understand available seats and fee structure.
I love what you guys are up too. Such clever work and exposure! Keep up the very good works guys I’ve incorporated you guys to my own blogroll. ยูฟ่าเบท168
Slot Maxwin adalah istilah yang digunakan dalam dunia permainan slot untuk merujuk pada kemenangan terbesar yang bisa diraih dalam satu kali putaran. Dengan strategi yang tepat dan pengelolaan modal yang bijak, meraih Slot Maxwin bukan lagi impian. Artikel ini akan mengupas berbagai langkah, tips, dan strategi untuk mendapatkan kemenangan maksimal di permainan slot, serta mengoptimalkan peluang untuk hasil yang memuaskan.
스포츠중계 무료 Your unique ability to illuminate complex concepts has made difficult ideas feel naturally approachable. The practical applications throughout help create lasting understanding that bridges theory and practice beautifully.
Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены. Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Contact us for Direct Admission in SIBM Pune now.
Your work is very good and I appreciate you and hopping for some more informative posts. 토토사이트
Thank you for creating a blog that not only informs but also motivates. Your positivity and поздравление с днем рождения enthusiasm are contagious. Your words have the power to uplift and inspire readers like me. Please keep spreading your light through your incredible blog!
Thanks for an interesting blog. What else may I get that sort of info written in such a perfect approach? I have an undertaking that I am just now operating on, and I have been on the lookout for such info.window replacement cost phoenix
It is ideal to read blogs like this. Have you ever wondered about the benefits of using Shower Toga? No worries if you haven't! It enhances your outdoor experience while promoting ethical and ecological practices like reducing water waste and offering an eco-conscious option. Experience a seamless, rejuvenating, and environmentally responsible outdoor shower solution. Get more details in the article.
Thanks for sharing such an informative post. Do you know guys how to view someone's private Instagram profiles? If you don't know, Don't worry. Now, you can view private Instagram profiles using the Instagram Private Profile Viewer free tool. More information about the tool is provided in the article. Visit the article and get more information about it.
ambbet-เว็บตรง ไม่มีการบริการใดเทียบเท่า ของเราอีกแล้ว เราพร้อมให้บริการทุกท่าน ทุกระดับประทับใจแน่นอน เรามีสิ่งอำนวยความสะดวกในการ ฝากถอน ด้วยระบบไอแบ้งกิ้ง สามารถเข้าเล่นได้ทุกที่ทุกเวลา
ambbet-plus เดิมพัน ของเราวันนี้มีโปรโมชั่นเพียบ สมัครเลย ด้วยเกมเดิมพันที่หลากหลายให้เล่น แทงบอลเป็นหลัก คาสิโนเป็นรอง พร้อมที่จะบริการลูกค้าทุกท่านให้ไปได้ไกลที่สุด แตกล้านจ่ายล้าน แน่นอนมั่นใจที่นี่ที่เดียว
ufabet168 vip เว็บตรงแทงบอล และคาสิโนออนไลน์ดีที่สุด สล็อต PG ทดลองเล่น เล่นฟรีทุกค่าย เล่นได้ก่อนใคร
มาเองงานนี้รับรองว่า ปลอดภัย 100% ลูกค้าจะได้รับประสบการ์ณการเดิมพันที่ดี เรามีโปรโมชั่นให้เลือกมากมาย พร้อมกับการบริการตอบแทนลูกค้าที่ไว้วางใจเล่นกับเราตลอดมา
หวยออนไลน์-เล่นยังไง
https://bigbrothertitansn.com/
bigbrothertitansn.com
Big Brother Titans is the joint South African and Nigerian edition of the Big Brother franchise. The theme for the first season was "Ziyakhala Wahala".
https://onlineiptvplayerz.com/
onlineiptvplayerz.com
IPTV is defined as the secure and reliable delivery to subscribers of entertainment video and related services.
https://dramaz24.com/
Watch best and latest K-Dramas on interenet online, without ads or anything for Free
dramaz24.com
Watch best and latest K-Dramas on interenet online, without ads or anything for Free.
https://bigbrothervipkosovalive.com/
bigbrothervipkosovalive.com
Kur do filloj big brother vip kosova dhe si do te jete edicioni i radhes? Njeri nder formatet me te medha televizive ne Kosove.
===
https://bbalbaniavip.com/
bbalbaniavip.com
Edicioni dhe teleshou me i ndjekur ne te gjitha trojet shqiptare Big Brother VIP Albania. Shiko Live Big Brother Albania LIVE ne webfaqen tone.
We still cannot quite believe that I was able to often be any type of those staring at the important points located on your blog post. 먹튀위키
Everyone is well aware of the importance of safety when
it comes to online sports betting. My website has a lot of
useful sports news information, check it out now.
토토사이트 안전성
يتميز فلتر مياه كوجين بفعاليته في تنقية المياه ب سعر فلتر كوجين المناسب، وهو ما يجعله الخيار المثالي لكل الفئات. ومن خلال ، يمكن الحصول على فلتر مياه كوجين 7 مراحل تايواني بسعر مناسب في عام 2023. بالإضافة إلى ذلك، يتميز هذا الفلتر بالهاوسنجات القوية والمضخة الفعالة، بالإضافة إلى أنه قابل للصيانة على المدى الطويل. كما يتضمن هذا الفلتر الشمعات القادرة على تنقية المياه بشكل مثالي، مما يجعله الخيار الأمثل لمن يبحثون عن جودة عالية بأسعار مناسبة. ويمكن الحصول على فلتر كوجين 7 مراحل تايواني عبر بسعر مناسب وبضمان الوكيل.3400 جنيه
https://waterfiltereg.com/
Your article is very good, I can help you if you need mobile sounds now free ringtones sonnerieandroid.com will help you.
https://fsonline.cc/
fsonline.cc
Filme si Seriale Online cu Subtitrare in Romana - FS Online Filme online HD si seriale online HD cu subtitrare in romana pe fsonlinehd
================================================================
https://yabancidizion.com/
yabancidizion.com
Yabancı Dizi – Yabancı Diziler ve Filmler Izle - Yabancı Dizi izle Türkçe Altyazılı Orjinal Diziler ve Türkçe Dublaj Yabancı Diziler.
==================================================================
https://turksub24.net/
turksub24.net
Turkish Series with English Subtitles - Full HD. Watch Turkish Series with English Subtitles for free!
---
https://diziizle.cc/
diziizle.cc
Dizi Izle – Diziler Izleme, Yerli Diziler, Full Dizi - Kaçırdığınız dizi bölümlerini ve sinema filmlerini full hd kalitesinde.
https://fsonline.cc/
fsonline.cc
Filme si Seriale Online cu Subtitrare in Romana - FS Online Filme online HD si seriale online HD cu subtitrare in romana pe fsonlinehd
================================================================
https://yabancidizion.com/
yabancidizion.com
Yabancı Dizi – Yabancı Diziler ve Filmler Izle - Yabancı Dizi izle Türkçe Altyazılı Orjinal Diziler ve Türkçe Dublaj Yabancı Diziler.
==================================================================
https://turksub24.net/
turksub24.net
Turkish Series with English Subtitles - Full HD. Watch Turkish Series with English Subtitles for free!
---
https://diziizle.cc/
diziizle.cc
Dizi Izle – Diziler Izleme, Yerli Diziler, Full Dizi - Kaçırdığınız dizi bölümlerini ve sinema filmlerini full hd kalitesinde.
Pale skin was considered fashionable for both men and women. Lead-based cosmetics were used to achieve this look, which unfortunately had harmful health effects.
Glad to chat your blog, I seem to be forward to more reliable articles and I think we all wish to thank so many good articles, blog to share with us. Suika
Helpful information. Fortunate me I found your site unintentionally, and I am shocked why this coincidence did not took place earlier! I bookmarked it. ufabet168
I really appreciate this wonderful post that you have provided for us. I assure this would be beneficial for most of the people. ทางเข้า biobet" title="ทางเข้า biobet">ทางเข้า biobet
ufabet168 เว็บตรง เสถียร ใช้งานง่ายที่สุด
Hello. Cool article. There’s an issue with the website and you might want to test this… The browser is the marketplace chief and a large element of other folks will miss your great writing due to this problem. betflix vip
فلتر فلوكستك 7 مراحل فخر الصناعة التايواني
يتميز فلتر فلوكستك بالكفاءة والجوده العالمية
فهو يعمل بنظام التناضح العكسي RO SYSTEM
ويتميز فلتر فلوكس تك بجودة خاماته واحتوائه على شمعة الكالاين التي ترفع قلوية المياه وخزان تايواني أصلي ٣ طبقات ١٢ لتر وحنفية من الاستانلس ستيل الخالص غير القابل للصدا وشمعات من كربون جوز الهند ، كما أن موتور الفلتر وكل شمعة من شمعات عليها باركود دولي مرتبط بمصنع فلوكستك مباشرة ، ويمكنك فحص الباركود من خلال موبايلك والتأكد من أنه أصلي .
تعرف معنا على افضل خصومات وعروض سعر فلتر فلوكستك فى مصر
افضل الخصومات لتكييف تكييف يونيون اير 2023 استمتع الان مع شركة تكييف يونيون اير بافضل الخصوصات الجديده على كل الاجهزه التى توجد لدينا ، يعنى هتقدر تشترى الجهاز اللى نفسك فيه وباقل الاسعار اللى مش هتلاقيها غير بس عندنا . تمتعنا شركة تكييف يونيون اير بافضل العروض على الاجهزه المكيفه لانها تريد دائما توفير ما يرضى العميل وليس المال وده ما يجعلنا دائما الشركة الاولى فى مصر وخارجها . الان هتقدر تحصل على تكييف يونيون اير من اقرب فرع لكم لاننا بنوفر لكم فروع لنا فى جميع المحافظات يعنى هيكون متوافر فى كل مكان ، وهتحصل على كل العروض والخصومات التى نوفرها لكم من اى فرع معتمد لنا . خلى صيفك مميزه مع تكييفات يونيون اير وستكون دائما مستمتع باوقاتك مع اسرتك .
https://unionairemasr.com/
I am not sure where your getting your information, but good topic. I needs to spend some time learning much more or understanding more. 먹튀위키
فلاتر مياه كوجين: الحل الأمثل لتنقية وتحسين جودة المياه
يعد فلتر كوجين من الخيارات الرائدة للأسر والمؤسسات لتحسين جودة المياه التي يتناولونها يومياً. تجمع هذه الفلاتر بين التكنولوجيا المبتكرة والتصميم المتقدم لتقديم مياه صحية ونقية للشرب والاستخدام المنزلي.
تحليل سعر فلتر مياه عمومى
مواصفات فلتر مياه عمومى
جهاز شمعه
وحده تزيل الشواب والاتربه العلقه بالمياه
فلتر مياة مرحلة واحدة 20 بوصة مع شمعة بولى صنع فى تيوانى لتنقية مياة المنزل بالكامل ويتم تركيبة على الموتور او على الخزان الرئيسي للفيلا او العمارة او غسلات الملابس او الاطباق للحفاظ عليها والاطاله عمر الغسلات ولذلك لمنع الشواب والرمل
Thanks for your personal marvelous posting! I quite enjoyed reading it, you will be a great author. I will always bookmark your blog and will come back down the road. I want to encourage one to continue your great posts, have a nice morning! ufabet168
Raih Keuntungan Besama Cucukakek89 Situs Slot Online Gacor yang menyediakan Bonus Mingguan Setiap hari Selesa Sebagai bentuk appresiasi untuk member aktif yang sudah bermain di cucukakek89. Kunjungi Dan Mainkan di Website Cucukakek89 Terpercaya!!. Mainkan Segera Permainan dengan pilihan Game Terlengkap dengan minimal deposit 25rb.
This is My Website:
great content custom printed boxes
Best Dash Cam, DVR, Cheapest, Best 2024, Lowest Price; Most Features
After searching for a great site. I was so impressed to yours. This will probably give me ideas for my work. Thank you ufabet
Does your blog have a contact page? I’m having problems locating it but, I’d like to shoot you an email. I’ve got some ideas for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it develop over time.biogame
Does your blog have a contact page? I’m having problems locating it but, I’d like to shoot you an email. I’ve got some ideas for your blog you might be interested in hearing. Either way, great site and I look forward to seeing it develop over time.biogame
Impressive web site, Distinguished feedback that I can tackle. Im moving forward and may apply to my current job as a pet sitter, which is very enjoyable, but I need to additional expand. ยูฟ่าสล็อต
You there, this is really good post here. Thanks for taking the time to post such valuable information. water damage las vegas
russian blue kittens for sale
https://lovelyrussianblues.com/available-kittens/" rel="dofollow">russian blue kittens for sale near me
russian blue kittens
russian blue kittens for sale $400
I’m not that much of a online reader to be honest but your blogs really nice, keep it up! I’ll go ahead and bookmark your site to come back later. All the best betflik vip
sangat bermanfaat sekali informasinya
Great post! I am actually getting ready to across this information, is very helpful my friend. I also like to recommend a site that is all about buy telegram members. Keep up the good work you are doing here. betflix vip
Thank you for another excellent article. Where else could anybody get that kind oof information in such an ideall means of writing? I’ve a presentation next week, and I’m at the look for such info.ทางเข้า betflix
Bokep Binatang
Anjing Kurap
Bokep Anjing
I think your blog might be having browser compatibility issues. When I look at your website in Ie, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, terrific blog!betflixvip
I must say you’ve done a amazing job with this. In addition, the blog loads very fast for me on Chrome. Excellent Blog!
slot gacor
slot gacor
slot dana
slot kamboja
slot gacor
slot kamboja
slot gacor
A debt of gratitude is in order for sharing the information, keep doing awesome... I truly delighted in investigating your site. great asset... Infinite Craft
Hey what a brilliant post I have come across and believe me I have been searching out for this similar kind of post for past a week and hardly came across this. Thank you very much and will look for more postings from you. 中文補習
Thank you for another excellent article. Where else could anybody get that kind oof information in such an ideall means of writing? I’ve a presentation next week, and I’m at the look for such info. ufabet เว็บแม่
ChokoCovered Banana Marshmallow
Funfetti Marshmallow
Raspberry Ripple Marshmallow
Strawberry Cheesecake Marshmallow
Rainbow BubbleGum Marshmallow
Key Lime Marshmallow
Blueberry Pie Marshmallow
Fruity Pebblez Marshmallow
Watermelon Marshmallow
Birthdaycake Marshmallow
Blue M&M Sprinklez
Marshmallow Super Duper
Marshmallow Froot Loopz
Gumdropz Concord Grape
Sprinklez Gumdropz
Gumdropz poppin papaya
Gumdropz Mango Mayhem
Gumdropz Wacky Watermelon
Gumdropz Island Punch
Gumdropz Strawberry Splash
Gumdropz Berry Bonanza
Gumdropz Wild Berry
Torchiez Blazin Banana
Torchiez Red Hotz
Original Torchiez
Grapefruit Cooler
Sprinklez Marshmallow Madness
Sprinklez Creamy Peanut Butter
Sprinklez Peach Perfection
Sprinklez Cherry Lemonade
Sprinklez Sweet Tartz
Sprinklez Cotton Candy
Sprinklez Candy Apple Cannabis Strain
Original Sprinklez Brand
Sprinklez Mint Chip
Gumdropz kiwi cooler
Oreo Cookies Marshmallow
Sprinklez Pumpkin strain
RASPBERRY SWIRL MARSHMALLOW
Sprinklez Apple Pie
Torchiez Chili Mango
Marshmallow tripple stack smorez
Marshmallow Twisted
Gumdrops Mint Mojito
Sprinklez New York Cheesecake
Sprinklez Candied Yams
New York Marshmallow
Torchiez Fuego berry
Sprinklez Takeover
Gumdropz Cranberry blast
Torchiez Jalapeno plum
Lemon bomb Marshmallow
Sprinklez Blueberry Pancakes
Marshmallow Jet Puff
Marshmallow Coconut Royale
Gumdropz Tropical Snow
Marshmallow Cloudberry
Spiced Gingerbread Choco Chunk
Gumdropz Cantaloupe Dream
Choko Whip Swirl Marshmallow
Gumdropz Persian Peach
Sprinklez Oreo Cheesecake
Frosty Snowman Marshmallow
Sprinklez Bubblegum CottonCandy
Torchiez Fire Plum Crumble
Sprinklez MIllionaire Shortbread
Marshmallow Sparkling Champagne
Sprinklez Fruitty Pebles
Gumdropz Honeydrew Delight
Hot Honey Butter Blondies
Twisted Caribbean
Confetti Cookies Dough Cream
Cherry Ripe Marshmallow
Southern Buttermilk Pie
Peach Cobbler
Pink Vanilla Butter Cake
Brooklyn Blackout Cake
Hot Fudge Sundae
Caramel Apple Cheesecake
Mississipi Mud Pie
Fresh Baked Apple Crisp
Blueberry Shortcake
Juicy Yellow Watermelon
Strawberry Frosted Flakes Milkshake
Cookies & Cream Milkshake
Orange Creamsicle
White Chocolate Strawberry
Bubblegum
Dulce de Leche
Kiwi Strawberry Splash
Blazed Buttermilk Doughnuts
Deep Fried Oreo Cookies
Rainbow Sherbet Cotton Candy
Mandarin Lime
Strawberry Banana Pudding
Cookies Monster
M&M Cookie Dough Cream
Rainbow Confetti Birthday cake
S'mores Stuffed French Toast
Red Velvet Cheesecake
Fresas Con Crema
Fantastic Funfetti
Froot Loops Ice Cream
Neapolitan
Rainbow Bubblegum
Gumdropz Cranberry blast
Gumdrops Mint Mojito
Gumdropz kiwi cooler
Gumdropz Concord Grape
Sprinklez Gumdropz
Gumdropz poppin papaya
Gumdropz Mango Mayhem
Gumdropz Wacky Watermelon
Gumdropz Island Punch
Gumdropz Strawberry Splash
Gumdropz Berry Bonanza
Gumdropz Wild Berry
Torchiez Jalapeno plum
Torchiez Fuego berry
Torchiez Chili Mango
Torchiez Red Hotz
Original Torchiez
Grapefruit Cooler
Sprinklez Blueberry Pancakes
Sprinklez Takeover
Sprinklez Candied Yams
Sprinklez New York Cheesecake
Sprinklez Apple Pie
Sprinklez Pumpkin strain
Sprinklez Mint Chip
Blue M&M Sprinklez
Sprinklez Marshmallow Madness
Sprinklez Creamy Peanut Butter
Sprinklez Peach Perfection
Sprinklez Cherry Lemonade
Sprinklez Sweet Tartz
Sprinklez Cotton Candy
Sprinklez Candy Apple
Original Sprinklez Brand
Marshmallow Coconut Royale
Marshmallow Jet Puff
Lemon bomb Marshmallow
New York Marshmallow
Marshmallow Twisted
Marshmallow tripple stack smorez
RASPBERRY SWIRL MARSHMALLOW
Cookies Marshmallow
ChokoCovered Banana Marshmallow
Funfetti Marshmallow
Raspberry Ripple Marshmallow
Strawberry Cheesecake Marshmallow
Rainbow BubbleGum Marshmallow
Key Lime Marshmallow
Marshmallow Raspberry Ripple
Blueberry Pie Marshmallow
Fruity Pebblez Marshmallow
Watermelon Marshmallow
Gumdropz Persian Peach
Choko Whip Swirl Marshmallow
Gumdropz Cantaloupe Dream
Spiced Gingerbread Choco Chunk
Marshmallow Cloudberry
Gumdropz Tropical Snow
Watermelon Marshmallow
Birthdaycake Marshmallow
Marshmallow Super Duper
Marshmallow Froot Loopz
Frosty Snowman Marshmallow
Torchiez Fire Plum Crumble
Sprinklez Oreo Cheesecake
Sprinklez Bubblegum CottonCandy
Sprinklez MIllionaire Shortbread
Marshmallow Sparkling Champagne
Sprinklez Fruitty Pebles
Gumdropz Honeydrew Delight
Hot Honey Butter Blondies
Twisted Caribbean
Confetti Cookies Dough Cream
Cherry Ripe Marshmallow
Southern Buttermilk Pie
Peach Cobbler
Pink Vanilla Butter Cake
Brooklyn Blackout Cake
Hot Fudge Sundae
Caramel Apple Cheesecake
Mississipi Mud Pie
Fresh Baked Apple Crisp
Blueberry Shortcake
Juicy Yellow Watermelon
Strawberry Frosted Flakes Milkshake
Cookies & Cream Milkshake
Orange Creamsicle
White Chocolate Strawberry
Bubblegum
Dulce de Leche
Blazed Buttermilk Doughnuts
Kiwi Strawberry Splash
Deep Fried Oreo Cookies
Rainbow Sherbet Cotton Candy
Mandarin Lime
Strawberry Banana Pudding
Cookies Monster
M&M Cookie Dough Cream
Rainbow Confetti Birthday cake
S'mores Stuffed French Toast
Fresas Con Crema
Red Velvet Cheesecake
Fantastic Funfetti
Froot Loops Ice Cream
Neapolitan
Bubblegum Rainbow
misoprostol 200mg
cytotec 200mg
Mifegest 200mg tablet
dextroamphetamine (dexedrine) 10mg
bensedin (diazepam) 10mg
Adderall 5mg - 25mg Tablets
Codeine Phosphate 30mg Tablets
Dihydrocodeine
Oxycontin 80mg Tablet
Tramadol 100mg Tablets
Stilnox (Zolpidem) 10mg
Mifepristone 10mg Tablet
Oxycontin 80mg Tablet
Methadone 10mg
Pregabalin Capsules 30mg (Lyrica)
Seconal Sodium
Oxycodone 30mg
Valium (Diazepam)
Cialis 10mg Tablet
Subutex 8mg (Sublingual tablets)
Vicoprofen 7.5mg-200mg
Demerol (Meperidine) 50mg/30ml Ampul
Dilaudid (Hydromorphone) 8mg
Soma (Carisoprodol) 350mg
Suboxone
Ibuprofen 200mg
Xanax (Alprazolam)
Demerol (Meperidine) 100mg
Codeine 30mg Tablets
Arimidex (Anastrozole) 1mg
Percocet (oxycodone/acetaminophen) 5mg/325mg
Adderall XR 10mg
Codeine 30mg Tablets
Valium (Diazepam)
Demerol (Meperidine) 100mg
Ritalin 10mg
Roxicodone 30mg
mounjaro injection
wegovy weight loss
trimtone fat burner
Capsiplex BURN
PhenGold Weight Loss Supplement
phenq best diet pill
Metformin diabetes pills
Rybelsus (Semaglutide) weight loss tablets online
Saxenda (Liraglutide) weight loss injection
Ozempic weight loss injection
Phentermine
Orlistat weight loss
Ozempic
AGEN VIP SLOT GACOR YANG DAPAT MEMBANTU SOBAT MENDAPATKAN KEMENANGAN DENGAN MUDAH
AGEN VIP SLOT GACOR YANG DAPAT MEMBANTU SOBAT MENDAPATKAN KEMENANGAN DENGAN MUDAH
AGEN VIP SLOT GACOR YANG DAPAT MEMBANTU SOBAT MENDAPATKAN KEMENANGAN DENGAN MUDAH
Unprecedented blog. I enjoyed investigating your articles. This is to a great degree an awesome investigated for me. I have bookmarked it and I am suspecting examining new articles.brisbane pest control
Unprecedented blog. I enjoyed investigating your articles. This is to a great degree an awesome investigated for me. I have bookmarked it and I am suspecting examining new articles. brisbane pest control
THE ONE CARGO เว็บ นำเข้าสินค้าจากจีน สั่งสินค้าจากจีนในรูปแบบภาษาไทย ผู้ให้บริการ สั่งซื้อสินค้า และ จัดส่งสินค้าจากเว็บไซต์ร้านค้าออนไลน์อันดับ 1 ของประเทศจีน เช่น taobao, tmall , 1688.com ส่งตรงถึงหน้าบ้าน
Your insightful exploration truly encapsulates the essence of the topic. I appreciate how you've skillfully woven together various perspectives, offering a comprehensive understanding. Your engaging writing style effortlessly draws readers in, making complex concepts accessible. Allow me to share some information with you Planning your Turkey adventure from Egypt? Navigate the Turkey Visa from Egypt process smoothly with our comprehensive guide. Ensure a hassle-free journey with expert tips and essential information.
THE ONE CARGO พร้อมให้บริการ นำเข้าสินค้าจากจีน กับลูกค้าได้ทุกเมื่อ เนื่องจากเรามีทั้งบริการขนส่งทางรถจากจีนมาไทย ในราคาเริ่มต้นเพียง 49 บาท บริการขนส่งทางเรือ ชิปปิ้งจากจีนมาไทยเริ่มต้นเพียง 39 บาท และบริการจัดส่งในประเทศ โดยบริษัทขนส่งเอกชนที่คุณสามารถเลือกตามต้องการ พร้อมจัดส่งถึงบ้านทั่วประเทศไทย
THE ONE CARGO คาร์โก้ จีนคุณภาพ ที่ผู้ประกอบการไว้ใจ บริการนำเข้าสินค้าจากจีนมาไทย ส่งเร็วได้มาตรฐาน เปิดบริการทุกวัน นำเข้าสินค้าจากจีน เอกสารครบถ้วนถูกต้อง ใบขน ใบกำกับ ใบ Form E ขนส่งทางรถ ระยะเวลา 3-5 วัน
Such a well-written piece! Your expertise is evident, and I appreciate the valuable insights you've provided.
Gaming PC Canada
บริการ นำเข้าสินค้าจากจีน ขนส่งสินค้าทางเรือ ด้วยประสบการณ์ยาวนานนับ 10 ปี ในการนำเข้าสินค้าจากจีน จึงทำให้ THE ONE CARGO เป็นที่ 1 ในใจของผู้นำเข้าหรือร้านค้าพรีออเดอร์จากจีน ด้วยจำนวนเที่ยวเรือที่ออกถี่ จึงทำให้ผู้นำเข้าไม่ต้องรอรอบนาน พร้อมกระจายสินค้าทั่วไทย
Drag and drop functionality simplifies the installation of the Shadow Fight 2 Mod APK. Simply download the APK file, drag it to your Android device, and drop it into the appropriate folder. This mod offers unlimited coins, gems, and other features, enhancing your gameplay experience with powerful weapons and unlocked levels.
Explore why Gulaal is among the best clothing brands for women. Our curated collections offer timeless designs and superior quality for every occasion.
Gulaal offers a stunning selection of unstitched suits. Craft your ideal look with our luxurious fabrics and intricate designs for a truly bespoke fashion experience.
Step into effortless style with Gulaal's Ready To Wear collection. Our fashion-forward designs offer convenience without compromising on elegance.
เว็บตรง 100 แหล่งเดิมพันออนไลน์ที่ดีที่สุด
ufabet789 แหล่งเดิมพันออนไลน์ที่ดีที่สุด
SIBM Pune
Get Direct admission in Symbiosis Institute of Business Management (SIBM) Pune is possible through the management quota. Students interested in the MBA program must meet basic eligibility criteria. Although the fees are higher, direct admission allows students to bypass competitive entrance exams like SNAP for guaranteed entry.
SLS Pune
Get Direct Admission in Symbiosis Law School (SLS) Pune through management quota, ideal for students seeking entry without appearing for competitive exams like SLAT. Eligibility typically includes minimum academic qualifications, and the direct admission process requires consultation with the admissions office to understand available seats and fee structure.
SIT Pune
Get Direct admission at Symbiosis Institute of Technology (SIT) Pune is available for engineering aspirants who do not wish to take entrance exams like JEE. Admissions are offered through management quota, ensuring that eligible students can still secure a seat while paying higher tuition fees than regular admissions.
MIT WPU Pune
Get Direct Admission in MIT World Peace University (MIT WPU) Pune offers direct admission to various undergraduate and postgraduate courses. Students who missed the entrance exam deadlines can apply through the management quota. Eligibility criteria, like minimum percentage in qualifying exams, must be met, and fees for direct admissions tend to be higher.
DY Patil College Pune
Get Direct Admission in DY Patil College Pune provides direct admission through management quota for students across engineering, medical, and management programs. Students can skip competitive exams but must meet academic criteria. This route offers a quicker admission process, though the fee structure for these seats is generally on the higher side.
Sinhgad College Pune
Get Direct admission at Sinhgad College Pune is available via the management quota for undergraduate and postgraduate programs. Students who meet the minimum academic requirements can apply without appearing for entrance exams, though the fees for such admissions are typically higher than the standard admission process.
Amity University Noida
Get Direct Admission in Amity University Noida allows direct admission to its wide range of undergraduate and postgraduate programs through management quota. Students can avoid entrance exams like Amity-JEE or CAT, provided they meet basic eligibility criteria. The fee structure is higher, and seats are limited in popular programs.
Fergusson College Pune
Get Direct Admission in Fergusson College Pune offers limited direct admission options through its management quota. Students need to meet the minimum eligibility criteria, and this route allows them to skip competitive exams. Though direct admission ensures a seat, it comes with a higher fee structure and limited availability.
Bharati Vidyapeeth Pune
Get Direct admission at Bharati Vidyapeeth Pune is available in several undergraduate and postgraduate programs through management quota. Students can bypass entrance exams but need to meet academic requirements. This method ensures a reserved seat in the college, though it often involves higher fees than merit-based seats.
Symbiosis Pune
Get Direct Admission in Symbiosis University Pune offers direct admission through the management quota in many of its constituent colleges like SIBM, SIT, and SLS. Students must fulfill basic academic criteria, and this option allows them to avoid entrance exams like SNAP or SLAT, though the fees are significantly higher.
SIBM Pune
Get Direct admission in Symbiosis Institute of Business Management (SIBM) Pune is possible through the management quota. Students interested in the MBA program must meet basic eligibility criteria. Although the fees are higher, direct admission allows students to bypass competitive entrance exams like SNAP for guaranteed entry.
SLS Pune
Get Direct Admission in Symbiosis Law School (SLS) Pune through management quota, ideal for students seeking entry without appearing for competitive exams like SLAT. Eligibility typically includes minimum academic qualifications, and the direct admission process requires consultation with the admissions office to understand available seats and fee structure.
SIT Pune
Get Direct admission at Symbiosis Institute of Technology (SIT) Pune is available for engineering aspirants who do not wish to take entrance exams like JEE. Admissions are offered through management quota, ensuring that eligible students can still secure a seat while paying higher tuition fees than regular admissions.
MIT WPU Pune
Get Direct Admission in MIT World Peace University (MIT WPU) Pune offers direct admission to various undergraduate and postgraduate courses. Students who missed the entrance exam deadlines can apply through the management quota. Eligibility criteria, like minimum percentage in qualifying exams, must be met, and fees for direct admissions tend to be higher.
DY Patil College Pune
Get Direct Admission in DY Patil College Pune provides direct admission through management quota for students across engineering, medical, and management programs. Students can skip competitive exams but must meet academic criteria. This route offers a quicker admission process, though the fee structure for these seats is generally on the higher side.
Sinhgad College Pune
Get Direct admission at Sinhgad College Pune is available via the management quota for undergraduate and postgraduate programs. Students who meet the minimum academic requirements can apply without appearing for entrance exams, though the fees for such admissions are typically higher than the standard admission process.
Amity University Noida
Get Direct Admission in Amity University Noida allows direct admission to its wide range of undergraduate and postgraduate programs through management quota. Students can avoid entrance exams like Amity-JEE or CAT, provided they meet basic eligibility criteria. The fee structure is higher, and seats are limited in popular programs.
Fergusson College Pune
Get Direct Admission in Fergusson College Pune offers limited direct admission options through its management quota. Students need to meet the minimum eligibility criteria, and this route allows them to skip competitive exams. Though direct admission ensures a seat, it comes with a higher fee structure and limited availability.
Bharati Vidyapeeth Pune
Get Direct admission at Bharati Vidyapeeth Pune is available in several undergraduate and postgraduate programs through management quota. Students can bypass entrance exams but need to meet academic requirements. This method ensures a reserved seat in the college, though it often involves higher fees than merit-based seats.
Symbiosis Pune
Get Direct Admission in Symbiosis University Pune offers direct admission through the management quota in many of its constituent colleges like SIBM, SIT, and SLS. Students must fulfill basic academic criteria, and this option allows them to avoid entrance exams like SNAP or SLAT, though the fees are significantly higher.
При обработке событий, связанных с мышью, нужен кроссбраузерный способ получения координат курсора из события в обработчике. Кроме того, необходимо знать нажатую кнопку мыши.
SIBM Pune
Get Direct admission in Symbiosis Institute of Business Management (SIBM) Pune is possible through the management quota. Students interested in the MBA program must meet basic eligibility criteria. Although the fees are higher, direct admission allows students to bypass competitive entrance exams like SNAP for guaranteed entry.
SLS Pune
Get Direct Admission in Symbiosis Law School (SLS) Pune through management quota, ideal for students seeking entry without appearing for competitive exams like SLAT. Eligibility typically includes minimum academic qualifications, and the direct admission process requires consultation with the admissions office to understand available seats and fee structure.
I love what you guys are up too. Such clever work and exposure! Keep up the very good works guys I’ve incorporated you guys to my own blogroll. ยูฟ่าเบท168
최고의 베팅 모험을 원하신다면 지금 G2Gbet에 가입하세요 – 바카라, 거대한 보너스, 온라인 카지노의 짜릿함을 모두 경험하세요
Slot Maxwin adalah istilah yang digunakan dalam dunia permainan slot untuk merujuk pada kemenangan terbesar yang bisa diraih dalam satu kali putaran. Dengan strategi yang tepat dan pengelolaan modal yang bijak, meraih Slot Maxwin bukan lagi impian. Artikel ini akan mengupas berbagai langkah, tips, dan strategi untuk mendapatkan kemenangan maksimal di permainan slot, serta mengoptimalkan peluang untuk hasil yang memuaskan.
스포츠중계 무료 Your unique ability to illuminate complex concepts has made difficult ideas feel naturally approachable. The practical applications throughout help create lasting understanding that bridges theory and practice beautifully.
Отправить комментарий
Приветствуются комментарии:Для остальных вопросов и обсуждений есть форум.