Skip to content

Instantly share code, notes, and snippets.

@ryosukemori
Created November 24, 2017 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryosukemori/671a1c155c43db76941dfef23d6f3d71 to your computer and use it in GitHub Desktop.
Save ryosukemori/671a1c155c43db76941dfef23d6f3d71 to your computer and use it in GitHub Desktop.
JqueryUI依存の交換型並び替え機能 アップロード付き .js
/**
* class SortBox
* 要素の並び替えやアイテム追加・アップロードのできるボックスを実現する
*
* 依存: jquery-ui
* @author Nickel Lab. Ryosuke Mori
* @version 0.1.0
*/
(function(){
/**
* @constructor
*/
function SortBox()
{
/* Options */
this.options = {
//並び替え機能
sort: true,
//並び替え対象子要素
childrenTag: 'li',
//並び替え生成時のflex付与有無
flex: true,
//要素移動のアニメーションスピード
sortDuration: 'fast',
//アップロード機能
upload: true,
//アップロード先
uploadAction: location.href.split('?')[0] + '/upload',
//アップロード値
uploadData: {},
//アップロードファイルタイプ
fileType: 'image',
//許容ファイルサイズ(Byte)
allowFileSize: 1000000,
//アップロード完了後処理クロージャ
success: this.uploadSuccess,
//アップロード完了後処理終了時に追加処理を行う
successAfter: function(){},
//アップロード失敗後処理クロージャ
fail: this.uploadFail,
//ソート情報を保存する形式
sortSaveType: 'hidden',
//ソート情報を保存する名前
sortSaveName: 'sort_box_data',
//この機能を適用するボックスのセレクタ
boxSelector: '#sort-box',
//ファイルインプットのname属性
fileInputName: 'sort_box_file',
//ファイルインプットをクリックさせる要素のセレクタ
fileInputWrapper: '#sort-box [data-sort-box=file-wrapper]',
/* Draggable Options */
revertDuration: '200',
scroll: false,
opacity: 0.8,
zindex: 10,
}
/* val */
//ソート情報
this.sortData = new Array();
//ドラッグ中オブジェクト
this.$draggedObj = null;
//ドラッグ中オブジェクトの手前のオブジェクト
this.$prevObj = null;
//ドラッグ中オブジェクトの元座標
this.draggedDefPosition = null;
//元に戻すフラグ
this.revert = true;
}
/**
* 初期化
*/
SortBox.prototype.init = function(boxSelector,options)
{
//セレクタが指定されていれば設定する
if( typeof boxSelector === 'string' ){
this.options.boxSelector = boxSelector;
this.options.fileInputWrapper = boxSelector + ' [data-sort-box=file-wrapper]';
}
//子要素を予め想定して設定
var boxTag = $(this.options.boxSelector)[0].tagName;
switch( boxTag ){
case 'UL':
this.options.childrenTag = 'li';
break;
case 'DIV':
this.options.childrenTag = 'div';
break;
case 'OL':
this.options.childrenTag = 'li';
break;
case 'DL':
this.options.childrenTag = 'dd';
break;
case 'TR':
this.options.childrenTag = 'td';
break;
case 'FORM':
this.options.childrenTag = 'input';
break;
case 'MENU':
this.options.childrenTag = 'button';
break;
}
//オプションがあれば設定する
if( typeof options === 'object' ){
if( 'sort' in options ){
this.options.sort = options.sort;
}
if( 'childrenTag' in options ){
this.options.childrenTag = options.childrenTag;
}
if( 'flex' in options ){
this.options.flex = options.flex;
}
if( 'sortDuration' in options ){
this.options.sortDuration = options.sortDuration;
}
if( 'upload' in options ){
this.options.upload = options.upload;
}
if( 'uploadAction' in options ){
this.options.uploadAction = options.uploadAction;
}
if( 'uploadData' in options ){
this.options.uploadData = options.uploadData;
}
if( 'fileType' in options ){
this.options.fileType = options.fileType;
}
if( 'allowFileSize' in options ){
this.options.allowFileSize = options.allowFileSize;
}
if( 'success' in options ){
this.options.success = options.success;
}
if( 'successAfter' in options ){
this.options.successAfter = options.successAfter;
}
if( 'fail' in options ){
this.options.fail = options.fail;
}
if( 'sortSaveType' in options ){
this.options.sortSaveType = options.sortSaveType;
}
if( 'sortSaveName' in options ){
this.options.sortSaveName = options.sortSaveName;
}
if( 'fileInputName' in options ){
this.options.fileInputName = options.fileInputName;
}
if( 'fileInputWrapper' in options ){
this.options.fileInputWrapper = options.fileInputWrapper;
}
// Draggable Options
if( 'revertDuration' in options ){
this.options.revertDuration = options.revertDuration;
}
if( 'scroll' in options ){
this.options.scroll = options.scroll;
}
if( 'opacity' in options ){
this.options.opacity = options.opacity;
}
if( 'zindex' in options ){
this.options.zindex = options.zindex;
}
}
//複数箇所対応でクローン
var SortBox = $.extend(true, {}, this);
var $box = $(SortBox.options.boxSelector);
//ソート機能
SortBox.addSorter($box);
//アップローダ機能
SortBox.addUploader($box);
}
/**
* ドラック&ドロップでの並び替え機能をセットする
* @param {Jquery Object} $box 対象リストのボックス要素
*/
SortBox.prototype.addSorter = function($box)
{
var _self = this;
//オプションが有効な場合に追加する
if( !this.options.sort ){
return false;
}
//対象子要素
var $children = $box.children(this.options.childrenTag + '[data-id]');
if( $children.length < 1 ){
console.log('子要素を取得できませんでした。対象の子要素に data-id 属性はありますか?');
return false;
}
//cssの適用
if( this.options.flex == true ){
$box.css('display','flex');
$children.css({
'cursor':'pointer',
'position':'relative',
});
}
//ドラッグ機能をセットする
$children.draggable({
containment: this.options.boxSelector,
scroll: this.options.scroll,
opacity: this.options.opacity,
zIndex: this.options.zindex,
revertDuration: this.options.revertDuration,
start: function(){
_self.draggableStart($(this));
},
stop: function(){
_self.draggableStop($(this));
},
revert: function(){
return _self.draggableRevert($(this));
},
cancel: $(_self.options.fileInputWrapper)
});
//ドロップ機能をセットする
$children.droppable({
drop: function(){
_self.droppableDrop($(this),$box,$children);
},
});
}
/**
* ドラッグ開始時の処理
* @param {Jquery Object} $e トリガー元オブジェクト
*/
SortBox.prototype.draggableStart = function($e)
{
this.$draggedObj = $e;
this.$prevObj = $e.prev();
this.draggedDefPosition = $e.position();
this.revert = true;
}
/**
* ドラッグ終了時の処理
* @param {Jquery Object} $e トリガー元オブジェクト
*/
SortBox.prototype.draggableStop = function($e)
{
$e.draggable('enable');
}
/**
* 元の位置に戻る処理の有無を返す
* @param {Jquery Object} $e トリガー元オブジェクト
*/
SortBox.prototype.draggableRevert = function($e)
{
return this.revert;
}
/**
* ドロップ時の処理
* @param {Jquery Object} $e トリガー元オブジェクト
* @param {Jquery Object} $box クラス適用コンテナボックス
* @param {Array<Jquery Object>} $children 適用対象子要素配列
*/
SortBox.prototype.droppableDrop = function($e,$box,$children)
{
var _self = this;
$children.draggable('disable');
this.revert = false;
this.$draggedObj.animate({
top: $e.position().top - this.draggedDefPosition.top,
left: $e.position().left - this.draggedDefPosition.left,
},this.options.sortDuration,
function(){
//ドロップアニメーション後
$e.animate({
top: _self.draggedDefPosition.top - $e.position().top,
left: _self.draggedDefPosition.left - $e.position().left,
},_self.options.sortDuration,
function(){
//要素の入れ替え
$e.before(_self.$draggedObj);
_self.$draggedObj.css({'top':0,'left':0});
if( _self.$prevObj.length != 0 ){
_self.$prevObj.after($e);
}
else{
$box.prepend($e);
}
$e.css({'top':0,'left':0});
//ソート状態の保存
_self.saveSortState($box);
//ソート状態をhtml出力
_self.outputSortState($box);
//ドラッグの有効化
$children.draggable('enable');
});
});
}
/**
* ソート情報を保存する
* @param {Jquery Object} $box クラス適用コンテナボックス
*/
SortBox.prototype.saveSortState = function($box)
{
var ids = $box.children('[data-id]').map(function(){ return $(this).attr('data-id') }).get();
this.sortData = ids;
return ids;
}
/**
* ソート情報をHTML出力する
* タイプは hidden, sameText,
* @param {Jquery Object} $box クラス適用コンテナボックス
* @return {Jquery Object} $sortData 生成した要素
*/
SortBox.prototype.outputSortState = function($box)
{
switch( this.options.sortSaveType ){
case 'hidden':
//Hiddenインプットを生成してボックス内に配置
var $sortData = $('<input>',{
'type': 'hidden',
'name': this.options.sortSaveName,
'class': 'sort-box-output',
'value': this.sortData.join(','),
});
break;
case 'childTagText':
//コンテナ内子要素と同じタグでテキスト出力する
var $sortData = $('<'+ this.options.childrenTag +'>',{
'class': 'sort-box-output',
});
$sortData.text(this.sortData.join(','));
break;
case 'childTagAttribute':
//コンテナ内子要素と同じタグで属性に出力する
var $sortdata = $('<'+ this.options.childrenTag +'>',{
'class': 'sort-box-output',
'sort-box-data': this.sortData.join(''),
});
break;
case 'none':
default:
//html出力をしない
return $('');
break;
}
if( $box.children('.sort-box-output').length > 0 ){
$box.children('.sort-box-output').replaceWith($sortData);
}
else{
$box.append($sortData);
}
return $sortData;
}
/**
* アップローダ機能をセットする
* @param {Jquery Object} $box クラス適用要素
*/
SortBox.prototype.addUploader = function($box)
{
var _self = this;
//オプションが有効な場合に追加する
if( !this.options.upload ){
return false;
}
//Input[type=file]を設置する
var $fileInput = $box.children('input[type=file]');
if( $fileInput.length < 1 ){
$fileInput = $('<input>',{
'type': 'file',
'name': this.options.fileInputName,
'style': 'display:none',
});
$box.append($fileInput);
}
//Input[type=file]をクリックする要素をセットする
var $wrapper = $( this.options.fileInputWrapper );
$wrapper.on('click',function(){
$fileInput.click();
});
//Input[type=file]のチェンジ後処理をセットする
$fileInput.on('change',function(){
_self.onChangeFileInput($(this));
});
}
/**
* アップローダ機能のファイルインプットの値変更時
* @param {Jquery Object} $e 呼び出し元要素
*/
SortBox.prototype.onChangeFileInput = function($e)
{
var _self = this;
var $fd = new FormData();
if( $e.val() == '' ){
console.log('アップロードファイルが存在しません。');
return false;
}
//Validation
var file = $e.prop('files')[0];
//ファイルサイズ
if( file.size >= this.options.allowFileSize ){
alert('ファイルサイズが'+ this.options.allowFileSize +'バイトを超えています');
return false;
}
//ファイルタイプ
if( file.type.indexOf(this.options.fileType) < 0 ){
alert('ファイルの種類が対象外です。');
return false;
}
//必要項目をフォームに追加
$fd.append("file",$e.prop("files")[0] );
$.each(this.options.uploadData,function(index,value){
$fd.append(index, value );
});
$.post({
url: this.options.uploadAction,
dataType: 'json',
data: $fd,
processData: false,
contentType: false,
},function(response){
//クロージャ def: this.uploadSuccess()
_self.options.success.call(_self,response,file);
})
.fail(function(response){
_self.options.fail.call(_self,response,file);
});
}
SortBox.prototype.uploadSuccess = function(response,file)
{
var _self = this;
var $box = $(this.options.boxSelector);
var fileReader = new FileReader();
//ファイル読み込完了時
fileReader.onload = function(fileData){
if( file.type.indexOf(_self.options.fileType) >= 0 ){
var $data = $('<img>',{
'src': fileData.target.result,
'title': file.name,
});
}
//TODO: text対応
//読み込みデータを表示
var $child = $('<'+ _self.options.childrenTag + '>',{
'class': 'sort-box-add',
'data-id': 'new',
});
$child.append($data);
$box.children('[data-id]:last').after($child);
_self.addSorter($box);
//ソート情報を更新
_self.saveSortState($box);
//ソート情報をHTMLに書き込み
_self.outputSortState($box);
//成功処理後に追加の処理
_self.options.successAfter.call(_self,response,file);
}
fileReader.readAsDataURL(file);
}
SortBox.prototype.uploadFail = function(response,file)
{
alert('アップロードに失敗しました。');
}
window.SortBox = SortBox;
}(window));
var SortBox = new SortBox();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment