in IE.
DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv],
{top: '0px', left: '0px', width: G.mapWidth + 'px', height: G.mapHeight +'px'});
};
//初始化style
DragZoomControl.prototype.initStyles_ = function(){
var G = this.globals;
DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv],
{filter: G.style.alphaIE, opacity: G.style.opacity, background:G.style.fillColor});
G.outlineDiv.style.border = G.style.border;
};
//框选button的click事件
DragZoomControl.prototype.butt
onclick _ = function(){
var G = this.globals;
G.backButtonDiv.style.display='none';
if (G.mapCover.style.display == 'block') { // reset if clicked before dragging
this.resetDragZoom_();
if (G.options.backButtonEnabled) {
this.restoreBackContext_(); // pop the backStack on a button reset
if (G.backStack.length==0) G.backButtonDiv.style.display='none';
}
} else {
this.initCover_();
if ( G.options.backButtonEnabled ) this.saveBackContext_(G.options.backButtonHTML,false); // save the map context for back button
}
};
//后退按钮的click事件
DragZoomControl.prototype.backbutt
onclick _ = function(){
var G = this.globals;
if (G.options.backButtonEnabled && G.backStack.length > 0) {
this.restoreBackContext_();
// invoke the callback if provided
if (G.callbacks['backbutt
onclick '] != null) {
G.callbacks.backbutt
onclick (G.methodCall);
}
}
};
//后退保存功能
DragZoomControl.prototype.saveBackContext_ = function(text,methodCall) {
var G = this.globals;
var backFrame = {};
backFrame["center"] = G.map.getCenter();
backFrame["zoom"] = G.map.getZoom();
backFrame["maptype"] = G.map.getCurrentMapType();
backFrame["text"] = G.backButtonDiv.innerHTML; // this saves the previous button text
backFrame["methodCall"] = methodCall; //This determines if it was an internal or method call
G.backStack.push(backFrame);
G.backButtonDiv.innerHTML = text;
// Back Button is turned on in resetDragZoom_()
};
//后退功能
DragZoomControl.prototype.restoreBackContext_ = function() {
var G = this.globals;
var backFrame = G.backStack.pop();
G.map.setCenter(backFrame["center"],backFrame["zoom"],backFrame["maptype"]);
G.backButtonDiv.innerHTML = backFrame["text"];
G.methodCall = backFrame["methodCall"];
if (G.backStack.length==0) G.backButtonDiv.style.display = 'none'; // if we're at the top of the stack, hide the back botton
};
//在地图上显示叠加层
DragZoomControl.prototype.initCover_ = function(){
var G = this.globals;
G.mapPosition = DragZoomUtil.getElementPosition(G.map.getContainer());
this.setDimensions_();
this.setButtonMode_('zooming');
DragZoomUtil.style([G.mapCover], {display: 'block', background: G.style.fillColor});
DragZoomUtil.style([G.outlineDiv], {width: '0px', height: '0px'});
//invoke callback if provided
if(G.callbacks['butt
onclick '] != null){
G.callbacks.butt
onclick ();
}
};
//获得鼠标的坐标
DragZoomControl.prototype.getRelPos_ = function(e) {
var pos = DragZoomUtil.getMousePosition(e);
var G = this.globals;
return {top: (pos.top - G.mapPosition.top),
left: (pos.left - G.mapPosition.left)};
};
//当用户拉框时求的矩形
DragZoomControl.prototype.getRectangle_=function(startX,startY,pos,ratio){
var left=false;
var top=false;
var dX=pos.left-startX;
var dY=pos.top-startY;
if(dX<0){
dX=dX*-1;
top=true;
}
delta=dX>dY?dX:dY;
return{
startX:startX,
startY:startY,
endX:startX+delta,
endY:startY+parseInt(delta*ratio),
width:delta,
height:parseInt(delta*ratio),
left:left,
top:top
}
};
//重置Buttonde的CSS
DragZoomControl.prototype.resetDragZoom_=function(){
var G=this.globals;
DragZoomUtil.style([G.mapCover,G.cornerTopDiv,G.cornerRightDiv,G.cornerBottomDiv,G.cornerLeftDiv],
{display:'none',opacity:G.style.opacity,fillter:G.style.alphaIE});
G.outlineDiv.style.display='none';
this.setButtonMode_('normal');
if(G.options.backButtonEnabled && (G.backStack.length>0))G.backButtonDiv.style.display='block';
};
var DragZoomUtil={};
//根据ID取得元素
DragZoomUtil.gE=function(sId){
return
document .getElementById(sId);
};
//取得DOM元素的绝对位置
DragZoomUtil.getMousePosition=function(e){
var posX=0;
var posY=0;
if(!e)var e=window.
event ;
if(e.pageX||e.pageY){
posX=e.pageX;
posY=e.pageY;
}else if(e.clientX||e.clientY){
posX=e.clientX+(
document .
document Element.scrollLeft?
document .
document Element.scrollLeft:
document .body.scrollLeft);
posY=e.clientY+(
document .
document Element.scrollTop?
document .
document Element.scrollTop:
document .body.scrollTop);
}
return {left:posX,top:posY};
};
//取得某一DOM元素的位置
DragZoomUtil.getElementPosition=function(element){
var leftPos=element.offsetLeft;
var topPos=element.offsetTop;
var parElement=element.offsetParent;
while(parElement!=null){
leftPos+=parElement.offsetLeft;
topPos+=parElement.offsetTop;
parElement=parElement.offsetParent;
}
return{left:leftPos,top:topPos};
};
//将style应用于DOM元素
DragZoomUtil.style=function(elements,styles){
if(typeof(elements)=='string'){
eiements=DragZoomUtil.getManyElements(elements);
}
for(var i=0;i
document.getElementById('divOutput').innerHTML="搜索中...";
var address=document .getElementById('txtAddress').value;
geocoder.getLocations(address,cb_showLocation);
}
function cb_showLocation(result){
//显示结果
if(result.Status.code==G_GEO_SUCCESS){
//成功表明
document .getElementById("divOutput").innerHTML="成功("+result.Placemark.length+") ";
for(var i=0;idocument.getElementById("divOutput").innerHTML+=((i+1)+""+
address+""+point.toString()+" ");
//(address"go ");
}//for
}//if
else{
document .getElementById("divOutput").innerHTML=result.Status.code;
}//else
}
/**
* 这个例子演示了 Google Map API 的以下功能:
* * 可拖拽的标注
* * 在地图上覆盖折线
* * 计算地理距离
* * 事件处理(单击、拖拽)
* * 消息提示窗口(气泡窗口)
* * 利用链表维护各种对象
* * 自定义控件
*
* 注意:为了在 IE6 中正常显示折线,必须在网页的 标签中加上:
*
*
* @author haogang
*/
/**
* 本示例用一个双向链表维护用户设定的标注,能够容易的实现标注的遍历和删除
* 每个链表结点 m 有如下字段:
* m.prev 前驱标注
* m.next 后继标注
* m.segPrev 连接本标注与前驱标注的线段
* m.segNext 连接本标注与后继标注的线段
*/
function GRulerControl() {
var me = this;
// 可国际化的字符串
me.RESET_BUTTON_TITLE_ = '清除所有测距标注';
me.ENABLE_BUTTON_TITLE_ = '添加测距标注已启用,单击这里禁用';
me.DISABLE_BUTTON_TITLE_ = '添加测距标注已禁用,单击这里启用';
me.DELETE_BUTTON_TITLE_ = '删除';
me.RESET_BUTTON_IMAGE_ = 'images/ruler_clear.png';
me.ENABLE_BUTTON_IMAGE_ = 'images/ruler_enabled.png';
me.DISABLE_BUTTON_IMAGE_ = 'images/ruler_disabled.png';
me.BACKGROUND_IMAGE_ = 'images/ruler_background.png'
me.KILOMETER_ = '公里';
me.METER_ = '米';
}
GRulerControl.prototype = new GControl();
/**
* 初始化标尺控件
*/
GRulerControl.prototype.initialize = function(map) {
var me = this;
var container = document .createElement('div');
/**
*默认位置在右上角
*/
GRulerControl.prototype.getDefaultPosition = function() {
return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(8, 30));
}
me.setButtonStyle_(container);
// “启用/禁用”按钮
var btnEnable = document .createElement('img');
btnEnable.width = btnEnable.height = 20;
GEvent .addDomListener(btnEnable, 'click',
function() {
me.setEnabled(!me.isEnabled());
}
);
container.appendChild(btnEnable);
// “重置”按钮
var btnReset = document .createElement('img');
btnReset.width = btnReset.height = 20;
btnReset.src = me.RESET_BUTTON_IMAGE_;
btnReset.title = me.RESET_BUTTON_TITLE_;
GEvent .addDomListener(btnReset, 'click',
function() {
me.reset();
}
);
container.appendChild(btnReset);
// 距离标签
var txtInfo = document .createElement('div');
txtInfo.style.font = 'small Arial';
txtInfo.style.fontWeight = 'bold';
txtInfo.style.fontSize = '9pt';
txtInfo.style.width = "82px";
container.appendChild(txtInfo);
// 初始化内部变量
map.rulerControl_ = me;
me.map_ = map;
me.head_ = new Object();
me.tail_ = new Object();
me.head_.next_ = me.tail_;
me.tail_.prev_ = me.head_;
me.btnEnable_ = btnEnable;
me.btnReset_ = btnReset;
me.txtInfo_ = txtInfo;
me.setEnabled(true);
map.getContainer().appendChild(container);
return container;
}
/**
* 设置控件的格式
*/
GRulerControl.prototype.setButtonStyle_ = function(button) {
button.style.backgroundImage = 'url(' + this.BACKGROUND_IMAGE_ + ')';
button.style.font = "small Arial";
button.style.border = "1px solid #888888";
button.style.padding = "4px";
button.style.textAlign = "right";
button.style.cursor = "pointer";
}
/**
* 用恰当的格式表示距离
*/
GRulerControl.prototype.formatDistance_ = function(len) {
var me = this;
len = Math.round(len);
if (len <= 1000) {
return len + ' ' + me.METER_;
} else if (len <= 1000000) {
return len / 1000 + ' ' + me.KILOMETER_;
}
return Math.round(len / 1000) + ' ' + me.KILOMETER_;
}
/**
* 格式化角度为字符串
*/
GRulerControl.prototype.formatDegree_ = function(value) {
value = Math.abs(value);
var v1 = Math.floor(value);
var v2 = Math.floor((value - v1) * 60);
var v3 = Math.round((value - v1) * 3600 % 60);
return v1 + '°' + v2 + '\'' + v3 + '"';
}
/**
* 格式化经纬度为字符串
*/
GRulerControl.prototype.formatLatLng_ = function(pt) {
var me = this;
var latName, lngName;
var lat = pt.lat();
var lng = pt.lng();
latName = lat >= 0 ? '北纬' : '南纬';
lngName = lng >= 0 ? '东经' : '西经';
return lngName + me.formatDegree_(lng) + ','
+ latName + me.formatDegree_(lat);
}
/**
* 返回控件的默认位置
*/
GRulerControl.prototype.getDefaultPosition = function() {
return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(8, 8));
}
/**
* 返回控件是否已启用
*/
GRulerControl.prototype.isEnabled = function() {
return this.enabled_;
}
/**
* 设置控件的“启用/禁用"状态
*/
GRulerControl.prototype.setEnabled = function(value) {
var me = this;
if (value == me.enabled_)
return;
me.enabled_ = value;
if (me.enabled_) {
me.mapClickHandle_ = GEvent .addListener(me.map_, 'click', me.onMapClick_);
me.txtInfo_.style.display = 'block';
me.btnReset_.style.display = 'inline';
me.btnEnable_.src = me.ENABLE_BUTTON_IMAGE_;
me.btnEnable_.title = me.ENABLE_BUTTON_TITLE_;
me.updateDistance_();
} else {
GEvent .removeListener(me.mapClickHandle_);
me.txtInfo_.style.display = 'none';
me.btnReset_.style.display = 'none';
me.btnEnable_.src = me.DISABLE_BUTTON_IMAGE_;
me.btnEnable_.title = me.DISABLE_BUTTON_TITLE_;
}
}
/**
* 事件处理函数:当用户单击地图时,要在该位置添加一个标注
*/
GRulerControl.prototype.onMapClick_ = function(marker, point) {
var me = this.rulerControl_;
// 如果用户单击的是标注,不再这里处理
if (marker)
return;
// 创建标注,并添加到链表中
var newMarker = new GMarker(point, {draggable: true});
var pos = me.tail_.prev_;
newMarker.prev_ = pos;
newMarker.next_ = pos.next_;
pos.next_.prev_ = newMarker;
pos.next_ = newMarker;
// 为标注添加事件处理函数:拖拽标注时要更新连接线段和距离
GEvent .addListener(newMarker, 'dragend',
function() {
me.map_.closeInfoWindow();
me.updateSegments_(newMarker);
me.updateDistance_();
}
);
// 为标注添加事件处理函数:单击标注时要显示气泡窗口
GEvent .addListener(newMarker, 'click',
function() {
newMarker.openInfoWindow(me.createInfoWindow_(newMarker));
}
);
// 将创建的标注添加到地图中
me.map_.addOverlay(newMarker);
if (newMarker.prev_ != me.head_) {
// 如果这不是第一个标注,则创建连接到上一个标注的线段,并显示在地图中
var segment = [newMarker.prev_.getPoint(), point];
newMarker.segPrev_ = new GPolyline(segment);
newMarker.prev_.segNext_ = newMarker.segPrev_;
me.map_.addOverlay(newMarker.segPrev_);
// 更新距离显示
me.updateDistance_();
}
}
/**
* 统计总距离,并显示在网页中
*/
GRulerControl.prototype.updateDistance_ = function() {
var me = this;
var len = me.getDistance();
// 结果显示在网页中
me.txtInfo_.innerHTML = me.formatDistance_(len);
}
/**
* 遍历链表,统计总距离
*/
GRulerControl.prototype.getDistance = function() {
var me = this;
var len = 0;
// 周游链表,累计相邻两个标注间的距离
for (var m = me.head_; m != me.tail_; m = m.next_) {
if (m.prev_ && m.prev_.getPoint)
len += m.prev_.getPoint().distanceFrom(m.getPoint());
}
return len;
}
/**
* 清除所有标注,初始化链表
*/
GRulerControl.prototype.reset = function() {
var me = this;
for (var m = me.head_.next_; m != me.tail_; m = m.next_) {
me.map_.removeOverlay(m);
if (m.segNext_)
me.map_.removeOverlay(m.segNext_);
}
me.head_ = new Object();
me.tail_ = new Object();
me.head_.next_ = me.tail_;
me.tail_.prev_ = me.head_;
me.updateDistance_();
}
/**
* 事件处理函数:当用户拖拽标注、标注坐标改变时被调用,这里要更新与该标注连接的线段
* @param {GMarker} marker 被拖拽的标注
*/
GRulerControl.prototype.updateSegments_ = function(marker) {
var me = this;
var segment;
// 更新连接前驱的线段
if (marker.segPrev_ && marker.prev_.getPoint) {
// 从地图上删除旧的线段
me.map_.removeOverlay(marker.segPrev_);
// 根据标注的当前坐标构造新的线段,并更新链表结点的相关字段
segment = [marker.prev_.getPoint(), marker.getPoint()];
marker.segPrev_ = new GPolyline(segment);
marker.prev_.segNext_ = marker.segPrev_;
// 将新线段添加到地图中
me.map_.addOverlay(marker.segPrev_);
}
// 更新连接后继的线段,与上类似
if (marker.segNext_ && marker.next_.getPoint) {
me.map_.removeOverlay(marker.segNext_);
segment = [marker.getPoint(), marker.next_.getPoint()];
marker.segNext_ = new GPolyline(segment);
marker.next_.segPrev_ = marker.segNext_;
me.map_.addOverlay(marker.segNext_);
}
}
/**
* 为气泡提示窗口创建 DOM 对象,包括标注的坐标和“删除”按钮
* @param {GMarker} marker 对应的标注
*/
GRulerControl.prototype.createInfoWindow_ = function(marker) {
var me = this;
// 为气泡提示窗口创建动态 DOM 对象,这里我们用 DIV 标签
var div = document .createElement('div');
div.style.fontSize = '10.5pt';
div.style.width = '250px';
div.appendChild(
document .createTextNode(me.formatLatLng_(marker.getPoint())));
var hr = document .createElement('hr');
hr.style.border = 'solid 1px #cccccc';
div.appendChild(hr);
// 创建“删除”按钮
var lnk = document .createElement('div');
lnk.innerHTML = me.DELETE_BUTTON_TITLE_;
lnk.style.color = '#0000cc';
lnk.style.cursor = 'pointer';
lnk.style.textDecoration = 'underline';
// 为“删除”按钮添加事件处理:调用 removePoint() 并重新计算距离
lnk.onclick =
function() {
me.map_.closeInfoWindow();
me.removePoint_(marker);
me.updateDistance_();
};
div.appendChild(lnk);
// 当用户关闭气泡时 Google Map API 会自动释放该对象
return div;
}
/**
* 事件处理函数:当用户选择删除标注时被调用,这里要删除与该标注连接的线段
* @param {GMarker} marker 要删除的标注
*/
GRulerControl.prototype.removePoint_ = function(marker) {
var me = this;
// 先从地图上删除该标注
me.map_.removeOverlay(marker);
// 对于中间结点,还要把它的前驱和后继用线段连接起来
if (marker.prev_.getPoint && marker.next_.getPoint) {
var segment = [marker.prev_.getPoint(), marker.next_.getPoint()];
var polyline = new GPolyline(segment);
marker.prev_.segNext_ = polyline;
marker.next_.segPrev_ = polyline;
me.map_.addOverlay(polyline);
}
marker.prev_.next_ = marker.next_;
marker.next_.prev_ = marker.prev_;
if (marker.segPrev_)
me.map_.removeOverlay(marker.segPrev_);
if (marker.segNext_)
me.map_.removeOverlay(marker.segNext_);
}
function load(){
//检查浏览器的兼容性
if(GBrowserIsCompatible()){
//加载地图
map=new GMap2(document .getElementById("map_canvas"));
//设置地图的中心坐标
var center=new GLatLng(24.49933,118.13800);
map.setCenter(center,12);
//设置地图的缩放工具
map.setUIToDefault();
//添加缩略图
map.addControl(new GOverviewMapControl());
//激活地图的双击放大功能和支持滑轮缩放
map.enableDoubleClickZoom();
map.enableScrollWheelZoom();
//给地图添加右键菜单
createContextMenu(map);
//位于左上角
var topLeft=new GControlPosition(G_ANCHOR_TOP_LEFT,new GSize(0,0));
//添加地址导航控件
map.addControl(new GNavLabelControl(),topLeft);
//添加自定义的控件
map.addControl(new GRulerControl());
//定义一个框选缩放控件样式
var styleOpts={};
//定义框选控件的参数
var otherOpts={};
//设置按钮的名称
otherOpts.buttonHTML='框选缩放';
//设置点击后的名称
otherOpts.buttonZoomingHTML='请在地图上拉一个框';
//支持连续框选缩放
otherOpts.stickyZoomEnabled=true;
//设置拉框清除的时间间隔
otherOpts.overlayRemoveTime=60;
//拉框时地图的透明度,取值从0~1
styleOpts.opacity=0;
//支持右键啦框缩小地图
otherOpts.rightMouseZoomOutEnabled=true;
var zcontrol=new DragZoomControl(styleOpts,otherOpts,{});
//位于左下角
var bottomLeft=new GControlPosition(G_ANCHOR_BOTTOM_LEFT,new GSize(0,0));
//添加自定义的控件
map.addControl(zcontrol,bottomLeft);
//初始化GClientGeocoder对象
GoogleGeocoder=new GClientGeocoder();
var icon=new GIcon();
html='这是什么
';
icon.image="http://labs.google.com/ridefinder/images/mm_20_red.png";
icon.iconSize=new GSize(21,29);
var marker=createMarker(point,icon,html);
var point=marker.getPoint(112.429714,39.934522);
map.addOverlay(marker);
geocoder=new GClientGeocoder();
var marker=new GMarker(center,{draggable:true});
map.addOverlay(marker);
document .getElementById("lat").innerHTML=center.lat().toFixed(5);
document .getElementById("lng").innerHTML=center.lng().toFixed(5);
GEvent .addListener(marker,"dragend",function(){
var point=marker.getPoint();
map.panTo(point);
document .getElementById("lat").innerHTML=point.lat().toFixed(5);
document .getElementById("lng").innerHTML=point.lng().toFixed(5);
});
GEvent .addListener(map,"moveend",function(){
map.clearOverlays();
var center=map.getCenter();
var marker=new GMarker(center,{draggable:true});
map.addOverlay(marker);
document .getElementById("lat").innerHTML=center.lat().toFixed(5);
document .getElementById("lng").innerHTML=center.lng().toFixed(5);
GEvent .addListener(marker,"dragend",function(){
var point=marker.getPoint();
map.panTo(point);
document .getElementById("lat").innerHTML=point.lat().toFixed(5);
document .getElementById("lng").innerHTML=point.lng().toFixed(5);
});
});
}
else
alert("error");
}
//创建右键菜单,参数为地图对象,即为该地图添加右键菜单功能
function createContextMenu(map){
//右键菜单其实是一个DIV
contextmenu=document .createElement("div");
//初始创建时右键菜单不可见
contextmenu.style.visibility="hidden";
//设置右键菜单的背景色及宽度
contextmenu.style.background="#ffffff";
contextmenu.style.border="1px solid #8888FF";
//获取地图对象的容器,并将菜单的DIV添加上去,但此时的菜单中不可见的
map.getContainer().appendChild(contextmenu);
//为地图的右键添加事件侦听。当右键点击地图时,在地图右键点击的地方把菜单显示出来
GEvent .addListener(map,"singlerightclick",function(pixel,title){
//获取右键点击时的坐标位置(指像素)
clickedPixel=pixel;
var x=pixel.x;
var y=pixel.y;
//如果右键点击达到了屏幕的边界,调整菜单的位置
if(x>map.getSize().width-120)
{
x=map.getSize().width-120
}
if(y>map.getSize().height-100)
{
y=map.getSize().height-100
}
//实例化一个地图的位置控件
var pos=new GControlPosition(G_ANCHOR_TOP_LEFT,new GSize(x,y));
//把菜单DIV作为对象传递给地图位置控件
pos.apply(contextmenu);
//把菜单设置为可见
contextmenu.style.visibility="visible";
});
//为地图添加click事件侦听器,当单击地图时,把菜单隐藏
GEvent .addListener(map,"click",function(){
contextmenu.style.visiblity="hidden";
});
}
function createMarker(point,baseIcon,html){
var icon=new GIcon(baseIcon);
var marker=new GMarker(point,icon);
GEvent .addListener(marker,"click",function(){
marker.openInfoWindowHtml(html);
});
return marker;
}
//初始化地图
window.onload=load;
//卸载地图
window.onunload=GUnload;
厦门旅游服务系统
查询地址:
onclick="showLocation();"/>
提示:单击您的当前位置和每个便民设施标注点通过测量距离选择您要的路线
用鼠标拖拉地图中心的红色气球标记就可以显示所在位置的经纬度坐标数据
纬度:
经度:
# Sortable
Sortable is a minimalist JavaScript library for reorderable drag-and-drop lists.
Demo: http://rubaxa.github.io/Sortable/
## Features
* Supports touch devices and [modern](http://caniuse.com/#search=drag) browsers (including IE9)
* Can drag from one list to another or within the same list
* CSS animation when moving items
* Supports drag handles *and selectable text* (better than voidberg's html5sortable)
* Smart auto-scrolling
* Built using native HTML5 drag and drop API
* Supports
* [Meteor](https://github.com/SortableJS/meteor-sortablejs)
* AngularJS
* [2.0+](https://github.com/SortableJS/angular-sortablejs)
* [1.*](https://github.com/SortableJS/angular-legacy-sortablejs)
* React
* [ES2015+](https://github.com/SortableJS/react-sortablejs)
* [Mixin](https://github.com/SortableJS/react-mixin-sortablejs)
* [Knockout](https://github.com/SortableJS/knockout-sortablejs)
* [Polymer](https://github.com/SortableJS/polymer-sortablejs)
* [Vue](https://github.com/SortableJS/Vue.Draggable)
* Supports any CSS library, e.g. [Bootstrap](#bs)
* Simple API
* [CDN](#cdn)
* No jQuery (but there is [support](#jq))
### Articles
* [Sortable v1.0 — New capabilities](https://github.com/RubaXa/Sortable/wiki/Sortable-v1.0-—-New-capabilities/) (December 22, 2014)
* [Sorting with the help of HTML5 Drag'n'Drop API](https://github.com/RubaXa/Sortable/wiki/Sorting-with-the-help-of-HTML5-Drag'n'Drop-API/) (December 23, 2013)
### Install
Via npm
```bash
$ npm install sortablejs --save
```
Via bower:
```bash
$ bower install --save sortablejs
```
### Usage
```html
```
```js
var el = document .getElementById('items');
var sortable = Sortable.create(el);
```
You can use any element for the list and its elements, not just `ul`/`li`. Here is an [example with `div`s](http://jsbin.com/qumuwe/edit?html,js,output).
---
### Options
```js
var sortable = new Sortable(el, {
group: "name", // or { name: "...", pull: [true, false, clone], put: [true, false, array] }
sort: true, // sorting inside list
delay: 0, // time in milliseconds to define when the sorting should start
touchStartThreshold: 0, // px, how many pixels the point should move before cancelling a delayed drag event
disabled: false, // Disables the sortable if set to true.
store: null, // @see Store
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
handle: ".my-handle", // Drag handle selector within list items
filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function)
prevent OnFilter: true, // Call `event .prevent Default()` when triggered `filter`
draggable: ".item", // Specifies which items inside the element should be draggable
ghostClass: "sortable-ghost", // Class name for the drop placeholder
chosenClass: "sortable-chosen", // Class name for the chosen item
dragClass: "sortable-drag", // Class name for the dragging item
dataIdAttr: 'data-id',
forceFallback: false, // ignore the HTML5 DnD behaviour and force the fallback to kick in
fallbackClass: "sortable-fallback", // Class name for the cloned DOM Element when using forceFallback
fallbackOnBody: false, // Appends the cloned DOM Element into the Document 's Body
fallbackTolerance: 0, // Specify in pixels how far the mouse should move before it's considered as a drag.
scroll: true, // or HTMLElement
scrollFn: function(offsetX, offsetY, originalEvent , touchEvt, hoverTargetEl) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling
scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling.
scrollSpeed: 10, // px
setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) {
dataTransfer.setData('Text', dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
},
// Element is chosen
onChoose: function (/**Event */evt) {
evt.oldIndex; // element index within parent
},
// Element dragging started
onStart: function (/**Event */evt) {
evt.oldIndex; // element index within parent
},
// Element dragging ended
onEnd: function (/**Event */evt) {
var itemEl = evt.item; // dragged HTMLElement
evt.to; // target list
evt.from; // previous list
evt.oldIndex; // element's old index within old parent
evt.newIndex; // element's new index within new parent
},
// Element is dropped into the list from another list
onAdd: function (/**Event */evt) {
// same properties as onEnd
},
// Changed sorting within list
onUpdate: function (/**Event */evt) {
// same properties as onEnd
},
// Called by any change to the list (add / update / remove)
onSort: function (/**Event */evt) {
// same properties as onEnd
},
// Element is removed from the list into another list
onRemove: function (/**Event */evt) {
// same properties as onEnd
},
// Attempt to drag a filtered element
onFilter: function (/**Event */evt) {
var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event .
},
// Event when you move an item in the list or between lists
onMove: function (/**Event */evt, /**Event */originalEvent ) {
// Example: http://jsbin.com/tuyafe/1/edit?js,output
evt.dragged; // dragged HTMLElement
evt.draggedRect; // TextRectangle {left, top, right и bottom}
evt.related; // HTMLElement on which have guided
evt.relatedRect; // TextRectangle
originalEvent .clientY; // mouse position
// return false; — for cancel
},
// Called when creating a clone of element
onClone: function (/**Event */evt) {
var origEl = evt.item;
var cloneEl = evt.clone;
}
});
```
---
#### `group` option
To drag elements from one list into another, both lists must have the same `group` value.
You can also define whether lists can give away, give and keep a copy (`clone`), and receive elements.
* name: `String` — group name
* pull: `true|false|'clone'|function` — ability to move from the list. `clone` — copy the item, rather than move.
* put: `true|false|["foo", "bar"]|function` — whether elements can be added from other lists, or an array of group names from which elements can be taken.
* revertClone: `boolean` — revert cloned element to initial position after moving to a another list.
Demo:
- http://jsbin.com/naduvo/edit?js,output
- http://jsbin.com/rusuvot/edit?js,output — use of complex logic in the `pull` and` put`
- http://jsbin.com/magogub/edit?js,output — use `revertClone: true`
---
#### `sort` option
Sorting inside list.
Demo: http://jsbin.com/videzob/edit?html,js,output
---
#### `delay` option
Time in milliseconds to define when the sorting should start.
Demo: http://jsbin.com/xizeh/edit?html,js,output
---
#### `touchStartThreshold` option
This option is similar to `fallbackTolerance` option.
When the `delay` option is set, some phones with very sensitive touch displays like the Samsung Galaxy S8 will fire
unwanted touchmove event s even when your finger is not moving, resulting in the sort not triggering.
This option sets the minimum pointer movement that must occur before the delayed sorting is cancelled.
Values between 3 to 5 are good.
---
#### `disabled` options
Disables the sortable if set to `true`.
Demo: http://jsbin.com/xiloqu/edit?html,js,output
```js
var sortable = Sortable.create(list);
document .getElementById("switcher").onclick = function () {
var state = sortable.option("disabled"); // get
sortable.option("disabled", !state); // set
};
```
---
#### `handle` option
To make list items draggable, Sortable disables text selection by the user.
That's not always desirable. To allow text selection, define a drag handler,
which is an area of every list element that allows it to be dragged around.
Demo: http://jsbin.com/newize/edit?html,js,output
```js
Sortable.create(el, {
handle: ".my-handle"
});
```
```html
:: list item text one
:: list item text two
```
```css
.my-handle {
cursor: move;
cursor: -webkit-grabbing;
}
```
---
#### `filter` option
```js
Sortable.create(list, {
filter: ".js-remove, .js-edit",
onFilter: function (evt) {
var item = evt.item,
ctrl = evt.target;
if (Sortable.utils.is(ctrl, ".js-remove")) { // Click on remove button
item[removed].removeChild(item); // remove sortable item
}
else if (Sortable.utils.is(ctrl, ".js-edit")) { // Click on edit link
// ...
}
}
})
```
---
#### `ghostClass` option
Class name for the drop placeholder (default `sortable-ghost`).
Demo: http://jsbin.com/hunifu/4/edit?css,js,output
```css
.ghost {
opacity: 0.4;
}
```
```js
Sortable.create(list, {
ghostClass: "ghost"
});
```
---
#### `chosenClass` option
Class name for the chosen item (default `sortable-chosen`).
Demo: http://jsbin.com/hunifu/3/edit?html,css,js,output
```css
.chosen {
color: #fff;
background-color: #c00;
}
```
```js
Sortable.create(list, {
delay: 500,
chosenClass: "chosen"
});
```
---
#### `forceFallback` option
If set to `true`, the Fallback for non HTML5 Browser will be used, even if we are using an HTML5 Browser.
This gives us the possibility to test the behaviour for older Browsers even in newer Browser, or make the Drag 'n Drop feel more consistent between Desktop , Mobile and old Browsers.
On top of that, the Fallback always generates a copy of that DOM Element and appends the class `fallbackClass` defined in the options. This behaviour controls the look of this 'dragged' Element.
Demo: http://jsbin.com/yacuqib/edit?html,css,js,output
---
#### `fallbackTolerance` option
Emulates the native drag threshold. Specify in pixels how far the mouse should move before it's considered as a drag.
Useful if the items are also clickable like in a list of links.
When the user clicks inside a sortable element, it's not uncommon for your hand to move a little between the time you press and the time you release.
Dragging only starts if you move the pointer past a certain tolerance, so that you don't accidentally start dragging every time you click.
3 to 5 are probably good values.
---
#### `scroll` option
If set to `true`, the page (or sortable-area) scrolls when coming to an edge.
Demo:
- `window`: http://jsbin.com/tutuzeh/edit?html,js,output
- `overflow: hidden`: http://jsbin.com/kolisu/edit?html,js,output
---
#### `scrollFn` option
Defines function that will be used for autoscrolling. el.scrollTop/el.scrollLeft is used by default.
Useful when you have custom scrollbar with dedicated scroll function.
---
#### `scrollSensitivity` option
Defines how near the mouse must be to an edge to start scrolling.
---
#### `scrollSpeed` option
The speed at which the window should scroll once the mouse pointer gets within the `scrollSensitivity` distance.
---
### Event object ([demo](http://jsbin.com/xedusu/edit?js,output))
- to:`HTMLElement` — list, in which moved element.
- from:`HTMLElement` — previous list
- item:`HTMLElement` — dragged element
- clone:`HTMLElement`
- oldIndex:`Number|undefined ` — old index within parent
- newIndex:`Number|undefined ` — new index within parent
#### `move` event object
- to:`HTMLElement`
- from:`HTMLElement`
- dragged:`HTMLElement`
- draggedRect:` TextRectangle`
- related:`HTMLElement` — element on which have guided
- relatedRect:` TextRectangle`
---
### Method
##### option(name:`String`[, value:`*`]):`*`
Get or set the option.
##### closest(el:`String`[, selector:`HTMLElement`]):`HTMLElement|null`
For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
##### toArray():`String[]`
Serializes the sortable's item `data-id`'s (`dataIdAttr` option) into an array of string.
##### sort(order:`String[]`)
Sorts the elements according to the array.
```js
var order = sortable.toArray();
sortable.sort(order.reverse()); // apply
```
##### save()
Save the current sorting (see [store](#store))
##### destroy()
Removes the sortable functionality completely.
---
### Store
Saving and restoring of the sort.
```html
```
```js
Sortable.create(el, {
group: "localStorage-example",
store: {
/**
* Get the order of elements. Called once during initialization.
* @param {Sortable} sortable
* @returns {Array}
*/
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : [];
},
/**
* Save the order of elements. Called onEnd (when the item is dropped).
* @param {Sortable} sortable
*/
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|'));
}
}
})
```
---
### Bootstrap
Demo: http://jsbin.com/qumuwe/edit?html,js,output
```html
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- Latest Sortable -->
[removed][removed]
<!-- Simple List -->
This is Sortable
It works with Bootstrap...
...out of the box.
It has support for touch devices.
Just drag some elements around.
[removed]
// Simple list
Sortable.create(simpleList, { /* options */ });
[removed]
```
---
### Static methods & properties
##### Sortable.create(el:`HTMLElement`[, options:`Object`]):`Sortable`
Create new instance.
---
##### Sortable.active:`Sortable`
Link to the active instance.
---
##### Sortable.utils
* on(el`:HTMLElement`,
event `:String`, fn`:Function`) — attach an
event handler function
* off(el`:HTMLElement`,
event `:String`, fn`:Function`) — remove an
event handler
* css(el`:HTMLElement`)`:Object` — get the values of all the CSS properties
* css(el`:HTMLElement`, prop`:String`)`:Mixed` — get the value of style properties
* css(el`:HTMLElement`, prop`:String`, value`:String`) — set one CSS properties
* css(el`:HTMLElement`, props`:Object`) — set more CSS properties
* find(ctx`:HTMLElement`, tagName`:String`[, iterator`:Function`])`:Array` — get elements by tag name
* bind(ctx`:Mixed`, fn`:Function`)`:Function` — Takes a function and returns a new one that will always have a particular context
* is(el`:HTMLElement`, selector`:String`)`:Boolean` — check the current matched set of elements against a selector
* closest(el`:HTMLElement`, selector`:String`[, ctx`:HTMLElement`])`:HTMLElement|Null` — for each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree
* clone(el`:HTMLElement`)`:HTMLElement` — create a deep copy of the set of matched elements
* toggleClass(el`:HTMLElement`, name`:String`, state`:Boolean`) — add or remove one classes from each element
---
### CDN
```html
<!-- jsDelivr :: Sortable (https://www.jsdelivr.com/package/npm/sortablejs) -->
[removed][removed]
<!-- jsDelivr :: Sortable :: Latest (https://www.jsdelivr.com/package/npm/sortablejs) -->
[removed][removed]
```
---
### jQuery compatibility
To assemble plugin for jQuery, perform the following steps:
```bash
cd Sortable
npm install
grunt jquery
```
Now you can use `jquery.fn.sortable.js`:
(or `jquery.fn.sortable.min.js` if you run `grunt jquery:min`)
```js
$("#list").sortable({ /* options */ }); // init
$("#list").sortable("widget"); // get Sortable instance
$("#list").sortable("destroy"); // destroy Sortable instance
$("#list").sortable("{method-name}"); // call an instance method
$("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters
```
And `grunt jquery:mySortableFunc` → `jquery.fn.mySortableFunc.js`
---
### Contributing (Issue/PR)
Please, [read this](CONTRIBUTING.md).
---
## MIT LICENSE
Copyright 2013-2017 Lebedev Konstantin
http://rubaxa.github.io/Sortable/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated document ation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.