From 0855144a0aebcc9cf7f2afe43a054aae6cb857e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sch=C3=B6lling?= Date: Sun, 10 Jul 2011 19:37:51 +0200 Subject: [PATCH] Issue #842: Sortable aspects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manuel Schölling --- app/controllers/users_controller.rb | 6 +- app/models/aspect.rb | 2 +- app/models/user.rb | 10 +- app/views/aspects/_aspect_listings.haml | 14 ++- .../20110710102747_add_order_id_to_aspects.rb | 9 ++ db/schema.rb | 3 +- .../vendor/jquery-ui-1.8.9.custom.min.js | 99 ++++++++++++++++++- 7 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20110710102747_add_order_id_to_aspects.rb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 5c82269ff..a8b5dc4ca 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -22,9 +22,9 @@ class UsersController < ApplicationController def update password_changed = false - if u = params[:user] - @user = current_user + @user = current_user + if u = params[:user] u.delete(:password) if u[:password].blank? u.delete(:password_confirmation) if u[:password].blank? and u[:password_confirmation].blank? u.delete(:language) if u[:language].blank? @@ -49,6 +49,8 @@ class UsersController < ApplicationController flash[:error] = I18n.t 'users.update.language_not_changed' end end + elsif aspect_order = params[:reorder_aspects] + @user.reorder_aspects(aspect_order) end respond_to do |format| diff --git a/app/models/aspect.rb b/app/models/aspect.rb index 0955e637f..feeec4af1 100644 --- a/app/models/aspect.rb +++ b/app/models/aspect.rb @@ -15,7 +15,7 @@ class Aspect < ActiveRecord::Base validates_length_of :name, :maximum => 20 validates_uniqueness_of :name, :scope => :user_id, :case_sensitive => false - attr_accessible :name, :contacts_visible + attr_accessible :name, :contacts_visible, :order_id before_validation do name.strip! diff --git a/app/models/user.rb b/app/models/user.rb index d93087837..3b9abdd5e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,7 +33,7 @@ class User < ActiveRecord::Base has_many :invitations_from_me, :class_name => 'Invitation', :foreign_key => :sender_id, :dependent => :destroy has_many :invitations_to_me, :class_name => 'Invitation', :foreign_key => :recipient_id, :dependent => :destroy - has_many :aspects + has_many :aspects, :order => 'order_id ASC' has_many :aspect_memberships, :through => :aspects has_many :contacts has_many :contact_people, :through => :contacts, :source => :person @@ -367,4 +367,12 @@ class User < ActiveRecord::Base def remove_mentions Mention.where( :person_id => self.person.id).delete_all end + + def reorder_aspects(aspect_order) + i = 0 + aspect_order.each do |id| + self.aspects.find(id).update_attributes({ :order_id => i }) + i += 1 + end + end end diff --git a/app/views/aspects/_aspect_listings.haml b/app/views/aspects/_aspect_listings.haml index 82219dcc8..7d25d5dfa 100644 --- a/app/views/aspects/_aspect_listings.haml +++ b/app/views/aspects/_aspect_listings.haml @@ -11,7 +11,7 @@ %ul.sub_nav - for aspect in all_aspects - %li{:class => ("active" if params["a_id"].to_i == aspect.id)} + %li{:id => aspect.id, :class => ("active" if params["a_id"].to_i == aspect.id)} .edit = link_to image_tag("icons/pencil.svg", :height => 12), edit_aspect_path(aspect), :rel => "facebox" @@ -36,3 +36,15 @@ = only_sharing_count = t('contacts.index.only_sharing_with_me') + :javascript + $(document).ready(function() { + jQuery('#aspect_nav.left_nav .all_aspects .sub_nav').sortable({ + items: 'li[id]', + update: function(event, ui) { + var order = jQuery(this).sortable('toArray'); + var obj = { 'reorder_aspects': order, 'authenticity_token': '#{form_authenticity_token}', '_method': 'put' }; + jQuery.ajax('/user', { type: 'post', dataType: 'script', data: obj }); + } + }); + }); + diff --git a/db/migrate/20110710102747_add_order_id_to_aspects.rb b/db/migrate/20110710102747_add_order_id_to_aspects.rb new file mode 100644 index 000000000..ab70b1d32 --- /dev/null +++ b/db/migrate/20110710102747_add_order_id_to_aspects.rb @@ -0,0 +1,9 @@ +class AddOrderIdToAspects < ActiveRecord::Migration + def self.up + add_column :aspects, :order_id, :integer + end + + def self.down + remove_column :aspects, :order_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 536e3167e..1cafba535 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110707221112) do +ActiveRecord::Schema.define(:version => 20110710102747) do create_table "aspect_memberships", :force => true do |t| t.integer "aspect_id", :null => false @@ -40,6 +40,7 @@ ActiveRecord::Schema.define(:version => 20110707221112) do t.datetime "created_at" t.datetime "updated_at" t.boolean "contacts_visible", :default => true, :null => false + t.integer "order_id" end add_index "aspects", ["user_id", "contacts_visible"], :name => "index_aspects_on_user_id_and_contacts_visible" diff --git a/public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js b/public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js index c93e18a71..72b4f512c 100755 --- a/public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js +++ b/public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js @@ -342,4 +342,101 @@ var i={};i[g]=(f=="show"?b=="pos"?"+=":"-=":b=="pos"?"-=":"+=")+e;a.animate(i,{q */ (function(e){e.effects.transfer=function(a){return this.queue(function(){var b=e(this),c=e(a.options.to),d=c.offset();c={top:d.top,left:d.left,height:c.innerHeight(),width:c.innerWidth()};d=b.offset();var f=e('
').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); b.dequeue()})})}})(jQuery); -; \ No newline at end of file +; +/* + * jQuery UI Sortable 1.8.9 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Sortables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function($,undefined){$.widget("ui.sortable",$.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:'auto',cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:'> *',opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000},_create:function(){var o=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh();this.floating=this.items.length?(/left|right/).test(this.items[0].item.css('float')):false;this.offset=this.element.offset();this._mouseInit();},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var i=this.items.length-1;i>=0;i--) +this.items[i].item.removeData("sortable-item");return this;},_setOption:function(key,value){if(key==="disabled"){this.options[key]=value;this.widget() +[value?"addClass":"removeClass"]("ui-sortable-disabled");}else{$.Widget.prototype._setOption.apply(this,arguments);}},_mouseCapture:function(event,overrideHandle){if(this.reverting){return false;} +if(this.options.disabled||this.options.type=='static')return false;this._refreshItems(event);var currentItem=null,self=this,nodes=$(event.target).parents().each(function(){if($.data(this,'sortable-item')==self){currentItem=$(this);return false;}});if($.data(event.target,'sortable-item')==self)currentItem=$(event.target);if(!currentItem)return false;if(this.options.handle&&!overrideHandle){var validHandle=false;$(this.options.handle,currentItem).find("*").andSelf().each(function(){if(this==event.target)validHandle=true;});if(!validHandle)return false;} +this.currentItem=currentItem;this._removeCurrentsFromItems();return true;},_mouseStart:function(event,overrideHandle,noActivation){var o=this.options,self=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(event);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");$.extend(this.offset,{click:{left:event.pageX-this.offset.left,top:event.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(event);this.originalPageX=event.pageX;this.originalPageY=event.pageY;(o.cursorAt&&this._adjustOffsetFromHelper(o.cursorAt));this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!=this.currentItem[0]){this.currentItem.hide();} +this._createPlaceholder();if(o.containment) +this._setContainment();if(o.cursor){if($('body').css("cursor"))this._storedCursor=$('body').css("cursor");$('body').css("cursor",o.cursor);} +if(o.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",o.opacity);} +if(o.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",o.zIndex);} +if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!='HTML') +this.overflowOffset=this.scrollParent.offset();this._trigger("start",event,this._uiHash());if(!this._preserveHelperProportions) +this._cacheHelperProportions();if(!noActivation){for(var i=this.containers.length-1;i>=0;i--){this.containers[i]._trigger("activate",event,self._uiHash(this));}} +if($.ui.ddmanager) +$.ui.ddmanager.current=this;if($.ui.ddmanager&&!o.dropBehaviour) +$.ui.ddmanager.prepareOffsets(this,event);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(event);return true;},_mouseDrag:function(event){this.position=this._generatePosition(event);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs;} +if(this.options.scroll){var o=this.options,scrolled=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!='HTML'){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-event.pageY=0;i--){var item=this.items[i],itemElement=item.item[0],intersection=this._intersectsWithPointer(item);if(!intersection)continue;if(itemElement!=this.currentItem[0]&&this.placeholder[intersection==1?"next":"prev"]()[0]!=itemElement&&!$.ui.contains(this.placeholder[0],itemElement)&&(this.options.type=='semi-dynamic'?!$.ui.contains(this.element[0],itemElement):true)){this.direction=intersection==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(item)){this._rearrange(event,item);}else{break;} +this._trigger("change",event,this._uiHash());break;}} +this._contactContainers(event);if($.ui.ddmanager)$.ui.ddmanager.drag(this,event);this._trigger('sort',event,this._uiHash());this.lastPositionAbs=this.positionAbs;return false;},_mouseStop:function(event,noPropagation){if(!event)return;if($.ui.ddmanager&&!this.options.dropBehaviour) +$.ui.ddmanager.drop(this,event);if(this.options.revert){var self=this;var cur=self.placeholder.offset();self.reverting=true;$(this.helper).animate({left:cur.left-this.offset.parent.left-self.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:cur.top-this.offset.parent.top-self.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){self._clear(event);});}else{this._clear(event,noPropagation);} +return false;},cancel:function(){var self=this;if(this.dragging){this._mouseUp({target:null});if(this.options.helper=="original") +this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");else +this.currentItem.show();for(var i=this.containers.length-1;i>=0;i--){this.containers[i]._trigger("deactivate",null,self._uiHash(this));if(this.containers[i].containerCache.over){this.containers[i]._trigger("out",null,self._uiHash(this));this.containers[i].containerCache.over=0;}}} +if(this.placeholder){if(this.placeholder[0].parentNode)this.placeholder[0].parentNode.removeChild(this.placeholder[0]);if(this.options.helper!="original"&&this.helper&&this.helper[0].parentNode)this.helper.remove();$.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){$(this.domPosition.prev).after(this.currentItem);}else{$(this.domPosition.parent).prepend(this.currentItem);}} +return this;},serialize:function(o){var items=this._getItemsAsjQuery(o&&o.connected);var str=[];o=o||{};$(items).each(function(){var res=($(o.item||this).attr(o.attribute||'id')||'').match(o.expression||(/(.+)[-=_](.+)/));if(res)str.push((o.key||res[1]+'[]')+'='+(o.key&&o.expression?res[1]:res[2]));});if(!str.length&&o.key){str.push(o.key+'=');} +return str.join('&');},toArray:function(o){var items=this._getItemsAsjQuery(o&&o.connected);var ret=[];o=o||{};items.each(function(){ret.push($(o.item||this).attr(o.attribute||'id')||'');});return ret;},_intersectsWith:function(item){var x1=this.positionAbs.left,x2=x1+this.helperProportions.width,y1=this.positionAbs.top,y2=y1+this.helperProportions.height;var l=item.left,r=l+item.width,t=item.top,b=t+item.height;var dyClick=this.offset.click.top,dxClick=this.offset.click.left;var isOverElement=(y1+dyClick)>t&&(y1+dyClick)l&&(x1+dxClick)item[this.floating?'width':'height'])){return isOverElement;}else{return(l0?"down":"up");},_getDragHorizontalDirection:function(){var delta=this.positionAbs.left-this.lastPositionAbs.left;return delta!=0&&(delta>0?"right":"left");},refresh:function(event){this._refreshItems(event);this.refreshPositions();return this;},_connectWith:function(){var options=this.options;return options.connectWith.constructor==String?[options.connectWith]:options.connectWith;},_getItemsAsjQuery:function(connected){var self=this;var items=[];var queries=[];var connectWith=this._connectWith();if(connectWith&&connected){for(var i=connectWith.length-1;i>=0;i--){var cur=$(connectWith[i]);for(var j=cur.length-1;j>=0;j--){var inst=$.data(cur[j],'sortable');if(inst&&inst!=this&&!inst.options.disabled){queries.push([$.isFunction(inst.options.items)?inst.options.items.call(inst.element):$(inst.options.items,inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'),inst]);}};};} +queries.push([$.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):$(this.options.items,this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'),this]);for(var i=queries.length-1;i>=0;i--){queries[i][0].each(function(){items.push(this);});};return $(items);},_removeCurrentsFromItems:function(){var list=this.currentItem.find(":data(sortable-item)");for(var i=0;i=0;i--){var cur=$(connectWith[i]);for(var j=cur.length-1;j>=0;j--){var inst=$.data(cur[j],'sortable');if(inst&&inst!=this&&!inst.options.disabled){queries.push([$.isFunction(inst.options.items)?inst.options.items.call(inst.element[0],event,{item:this.currentItem}):$(inst.options.items,inst.element),inst]);this.containers.push(inst);}};};} +for(var i=queries.length-1;i>=0;i--){var targetData=queries[i][1];var _queries=queries[i][0];for(var j=0,queriesLength=_queries.length;j=0;i--){var item=this.items[i];var t=this.options.toleranceElement?$(this.options.toleranceElement,item.item):item.item;if(!fast){item.width=t.outerWidth();item.height=t.outerHeight();} +var p=t.offset();item.left=p.left;item.top=p.top;};if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this);}else{for(var i=this.containers.length-1;i>=0;i--){var p=this.containers[i].element.offset();this.containers[i].containerCache.left=p.left;this.containers[i].containerCache.top=p.top;this.containers[i].containerCache.width=this.containers[i].element.outerWidth();this.containers[i].containerCache.height=this.containers[i].element.outerHeight();};} +return this;},_createPlaceholder:function(that){var self=that||this,o=self.options;if(!o.placeholder||o.placeholder.constructor==String){var className=o.placeholder;o.placeholder={element:function(){var el=$(document.createElement(self.currentItem[0].nodeName)).addClass(className||self.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!className) +el.style.visibility="hidden";return el;},update:function(container,p){if(className&&!o.forcePlaceholderSize)return;if(!p.height()){p.height(self.currentItem.innerHeight()-parseInt(self.currentItem.css('paddingTop')||0,10)-parseInt(self.currentItem.css('paddingBottom')||0,10));};if(!p.width()){p.width(self.currentItem.innerWidth()-parseInt(self.currentItem.css('paddingLeft')||0,10)-parseInt(self.currentItem.css('paddingRight')||0,10));};}};} +self.placeholder=$(o.placeholder.element.call(self.element,self.currentItem));self.currentItem.after(self.placeholder);o.placeholder.update(self,self.placeholder);},_contactContainers:function(event){var innermostContainer=null,innermostIndex=null;for(var i=this.containers.length-1;i>=0;i--){if($.ui.contains(this.currentItem[0],this.containers[i].element[0])) +continue;if(this._intersectsWith(this.containers[i].containerCache)){if(innermostContainer&&$.ui.contains(this.containers[i].element[0],innermostContainer.element[0])) +continue;innermostContainer=this.containers[i];innermostIndex=i;}else{if(this.containers[i].containerCache.over){this.containers[i]._trigger("out",event,this._uiHash(this));this.containers[i].containerCache.over=0;}}} +if(!innermostContainer)return;if(this.containers.length===1){this.containers[innermostIndex]._trigger("over",event,this._uiHash(this));this.containers[innermostIndex].containerCache.over=1;}else if(this.currentContainer!=this.containers[innermostIndex]){var dist=10000;var itemWithLeastDistance=null;var base=this.positionAbs[this.containers[innermostIndex].floating?'left':'top'];for(var j=this.items.length-1;j>=0;j--){if(!$.ui.contains(this.containers[innermostIndex].element[0],this.items[j].item[0]))continue;var cur=this.items[j][this.containers[innermostIndex].floating?'left':'top'];if(Math.abs(cur-base)this.containment[2])pageX=this.containment[2]+this.offset.click.left;if(event.pageY-this.offset.click.top>this.containment[3])pageY=this.containment[3]+this.offset.click.top;} +if(o.grid){var top=this.originalPageY+Math.round((pageY-this.originalPageY)/o.grid[1])*o.grid[1];pageY=this.containment?(!(top-this.offset.click.topthis.containment[3])?top:(!(top-this.offset.click.topthis.containment[2])?left:(!(left-this.offset.click.left=0;i--){if($.ui.contains(this.containers[i].element[0],this.currentItem[0])&&!noPropagation){delayedTriggers.push((function(c){return function(event){c._trigger("receive",event,this._uiHash(this));};}).call(this,this.containers[i]));delayedTriggers.push((function(c){return function(event){c._trigger("update",event,this._uiHash(this));};}).call(this,this.containers[i]));}};};for(var i=this.containers.length-1;i>=0;i--){if(!noPropagation)delayedTriggers.push((function(c){return function(event){c._trigger("deactivate",event,this._uiHash(this));};}).call(this,this.containers[i]));if(this.containers[i].containerCache.over){delayedTriggers.push((function(c){return function(event){c._trigger("out",event,this._uiHash(this));};}).call(this,this.containers[i]));this.containers[i].containerCache.over=0;}} +if(this._storedCursor)$('body').css("cursor",this._storedCursor);if(this._storedOpacity)this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=='auto'?'':this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!noPropagation){this._trigger("beforeStop",event,this._uiHash());for(var i=0;i