diff --git a/AUTHORS.md b/AUTHORS.md index 0b495e5b1..230f379a4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -70,4 +70,6 @@ Contributors to LibreNMS: - Richard Kojedzinszky (rkojedzinszky) - Tony Murray (murrant) - Peter Lamperud (vizay) +- Louis Bailleul (alucardfh) +- Rick Hodger (Tatermen) [1]: http://observium.org/ "Observium web site" diff --git a/Makefile b/Makefile index 92d8dddf1..1b168dd16 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ typeahead: $(GIT_SUBTREE) --prefix=lib/typeahead https://github.com/twitter/typeahead.js.git master gridster: - $(GIT_SUBTREE) --prefix=lib/gridster https://github.com/ducksboard/gridster.js.git master + $(GIT_SUBTREE) --prefix=lib/gridster https://github.com/dsmorse/gridster.js.git master jquery-mapael: $(GIT_SUBTREE) --prefix=lib/jQuery-Mapael https://github.com/neveldo/jQuery-Mapael.git master diff --git a/alerts.php b/alerts.php index d52361101..9d6defca5 100755 --- a/alerts.php +++ b/alerts.php @@ -314,6 +314,9 @@ function ExtTransports($obj) { $tmp = false; // To keep scrutinizer from naging because it doesnt understand eval foreach ($config['alert']['transports'] as $transport => $opts) { + if (is_array($opts)) { + $opts = array_filter($opts); + } if (($opts === true || !empty($opts)) && $opts != false && file_exists($config['install_dir'].'/includes/alerts/transport.'.$transport.'.php')) { echo $transport.' => '; eval('$tmp = function($obj,$opts) { global $config; '.file_get_contents($config['install_dir'].'/includes/alerts/transport.'.$transport.'.php').' return false; };'); diff --git a/daily.php b/daily.php index 8569f1c9a..546335a8b 100644 --- a/daily.php +++ b/daily.php @@ -13,10 +13,8 @@ require 'includes/functions.php'; $options = getopt('f:'); if ($options['f'] === 'update') { - $pool_size = dbFetchCell('SELECT @@innodb_buffer_pool_size'); - // The following query is from the excellent mysqltuner.pl by Major Hayden https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl - $pool_used = dbFetchCell('SELECT SUM(DATA_LENGTH+INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ("information_schema", "performance_schema", "mysql") AND ENGINE = "InnoDB" GROUP BY ENGINE ORDER BY ENGINE ASC'); - if ($pool_used > $pool_size) { + $innodb_buffer = innodb_buffer_check(); + if ($innodb_buffer['used'] > $innodb_buffer['size']) { if (!empty($config['alert']['default_mail'])) { $subject = $config['project_name'] . ' auto-update action required'; $message = ' @@ -26,19 +24,14 @@ We have just tried to update your installation but it looks like the InnoDB buff Because of this we have stopped the auto-update running to ensure your system is ok. -You currently have a configured innodb_buffer_pool_size of ' . $pool_size / 1024 / 1024 . ' MiB but is currently using ' . $pool_used / 1024 / 1024 . ' MiB +You currently have a configured innodb_buffer_pool_size of ' . $innodb_buffer['size'] / 1024 / 1024 . ' MiB but is currently using ' . $innodb_buffer['used'] / 1024 / 1024 . ' MiB Take a look at https://dev.mysql.com/doc/refman/5.6/en/innodb-buffer-pool.html for further details. The ' . $config['project_name'] . ' team.'; send_mail($config['alert']['default_mail'],$subject,$message,$html=false); - } else { - echo 'InnoDB Buffersize too small.'.PHP_EOL; - echo 'Current size: '.($pool_size / 1024 / 1024).' MiB'.PHP_EOL; - echo 'Minimum Required: '.($pool_used / 1024 / 1024).' MiB'.PHP_EOL; - echo 'To ensure integrity, we\'re not going to pull any updates until the buffersize has been adjusted.'.PHP_EOL; - echo 'Config proposal: "innodb_buffer_pool_size = '.pow(2,ceil(log(($pool_used / 1024 / 1024),2))).'M"'.PHP_EOL; - } + } + echo warn_innodb_buffer($innodb_buffer); exit(2); } else { @@ -103,3 +96,7 @@ if ($options['f'] === 'device_perf') { } } } + +if ($options['f'] === 'notifications') { + include_once 'notifications.php'; +} diff --git a/daily.sh b/daily.sh index ddc4f2e9f..effb4a502 100755 --- a/daily.sh +++ b/daily.sh @@ -16,3 +16,4 @@ php daily.php -f authlog php daily.php -f perf_times php daily.php -f callback php daily.php -f device_perf +php daily.php -f notifications diff --git a/doc/Extensions/Alerting.md b/doc/Extensions/Alerting.md index fe4e51dab..d87d30a11 100644 --- a/doc/Extensions/Alerting.md +++ b/doc/Extensions/Alerting.md @@ -18,6 +18,9 @@ Table of Content: - [Pushover](#transports-pushover) - [Boxcar](#transports-boxcar) - [Pushbullet](#transports-pushbullet) + - [Clickatell](#transports-clickatell) + - [PlaySMS](#transports-playsms) + - [VictorOps](#transports-victorops) - [Entities](#entities) - [Devices](#entity-devices) - [BGP Peers](#entity-bgppeers) @@ -372,6 +375,54 @@ $config['alert']['transports']['pushbullet'] = 'MYFANCYACCESSTOKEN'; ``` ~~ +## Clickatell + +Clickatell provides a REST-API requiring an Authorization-Token and at least one Cellphone number. +Please consult Clickatell's documentation regarding number formating. +Here an example using 3 numbers, any amount of numbers is supported: + +~~ +```php +$config['alert']['transports']['clickatell']['token'] = 'MYFANCYACCESSTOKEN'; +$config['alert']['transports']['clickatell']['to'][] = '+1234567890'; +$config['alert']['transports']['clickatell']['to'][] = '+1234567891'; +$config['alert']['transports']['clickatell']['to'][] = '+1234567892'; +``` +~~ + +## PlaySMS + +PlaySMS is an OpenSource SMS-Gateway that can be used via their HTTP-API using a Username and WebService-Token. +Please consult PlaySMS's documentation regarding number formating. +Here an example using 3 numbers, any amount of numbers is supported: + +~~ +```php +$config['alert']['transports']['playsms']['url'] = 'https://localhost/index.php?app=ws'; +$config['alert']['transports']['playsms']['user'] = 'user1'; +$config['alert']['transports']['playsms']['token'] = 'MYFANCYACCESSTOKEN'; +$config['alert']['transports']['playsms']['from'] = '+1234567892'; //Optional +$config['alert']['transports']['playsms']['to'][] = '+1234567890'; +$config['alert']['transports']['playsms']['to'][] = '+1234567891'; +``` +~~ + +## VictorOps + +VictorOps provide a webHook url to make integration extremely simple. To get the URL required login to your VictorOps account and go to: + +Settings -> Integrations -> REST Endpoint -> Enable Integration. + +The URL provided will have $routing_key at the end, you need to change this to something that is unique to the system sending the alerts such as librenms. I.e: + +`https://alert.victorops.com/integrations/generic/20132414/alert/2f974ce1-08fc-4dg8-a4f4-9aee6cf35c98/librenms` + +~~ +```php +$config['alert']['transports']['victorops']['url'] = 'https://alert.victorops.com/integrations/generic/20132414/alert/2f974ce1-08fc-4dg8-a4f4-9aee6cf35c98/librenms'; +``` +~~ + # Entities Entities as described earlier are based on the table and column names within the database, if you are unsure of what the entity is you want then have a browse around inside MySQL using `show tables` and `desc `. diff --git a/doc/Extensions/Proxmox.md b/doc/Extensions/Proxmox.md index 0691e1d27..4b7f8a2a1 100644 --- a/doc/Extensions/Proxmox.md +++ b/doc/Extensions/Proxmox.md @@ -1,6 +1,5 @@ # Proxmox graphing - -It is possible to create graphs of the Proxmox VMs that run on your monitored machines. Currently, only trafficgraphs are created. One for each interface on each VM. Possibly, IO grahps will be added later on. +It is possible to create graphs of the Proxmox **VMs** that run on your monitored machines. Currently, only trafficgraphs are created. One for each interface on each VM. Possibly, IO grahps will be added later on. The ultimate goal is to be able to create traffic bills for VMs, no matter on which physical machine that VM runs. @@ -10,14 +9,20 @@ To enable Proxmox graphs, do the following: In config.php, enable Proxmox: ```php -$config['enable_proxmox'] = 1 +$config['enable_proxmox'] = 1; ``` -Then, install librenms-agent on the machines running Proxmox, and enable the Proxmox-plugin using: +Then, install [librenms-agent](http://docs.librenms.org/Extensions/Agent-Setup/) on the machines running Proxmox and enable the Proxmox-script using: + ```bash -mk_enplug proxmox +cp /opt/librenms-agent/proxmox /usr/lib/check_mk_agent/local/proxmox +chmod +x /usr/lib/check_mk_agent/local/proxmox ``` -Then, enable the unix-agent on the machines running Proxmox. +Then, restart the xinetd service +```bash +/etc/init.d/xinetd restart +``` +Then in LibreNMS active the librenms-agent and proxmox application flag for the device you are monitoring. You should now see an application in LibreNMS, as well as a new menu-item in the topmenu, allowing you to choose which cluster you want to look at. diff --git a/doc/General/Callback-Stats-and-Privacy.md b/doc/General/Callback-Stats-and-Privacy.md index e0d32c7a7..3e043ea98 100644 --- a/doc/General/Callback-Stats-and-Privacy.md +++ b/doc/General/Callback-Stats-and-Privacy.md @@ -28,5 +28,6 @@ Now onto the bit you're interested in, what is submitted and what we do with tha Hopefully this answers the questions you might have on why and what we are doing here, if not, please pop into our irc channel or google mailing list and ask any questions you like. +#### How do I enable stats submission? #### If you're happy with all of this - please consider switching the call back system on, you can do this within the About LibreNMS page within your control panel. In the Statistics section you will find a toggle switch to enable / disable the feature. If you've previously had it switched on and want to opt out and remove your data, click the 'Clear remote stats' button and on the next submission all the data you've sent us will be removed! diff --git a/doc/Installation/Installation-(RHEL-CentOS).md b/doc/Installation/Installation-(RHEL-CentOS).md index 25fbc1db9..10767378d 100644 --- a/doc/Installation/Installation-(RHEL-CentOS).md +++ b/doc/Installation/Installation-(RHEL-CentOS).md @@ -99,7 +99,8 @@ Note if not using HTTPd (Apache): RHEL requires `httpd` to be installed regardle **CentOS 7** ```bash - yum install php php-cli php-gd php-mysql php-snmp php-pear php-curl httpd net-snmp graphviz graphviz-php mariadb ImageMagick jwhois nmap mtr rrdtool MySQL-python net-snmp-utils vixie-cron php-mcrypt fping git + yum install epel-release + yum install php php-cli php-gd php-mysql php-snmp php-pear php-curl httpd net-snmp graphviz graphviz-php mariadb ImageMagick jwhois nmap mtr rrdtool MySQL-python net-snmp-utils cronie php-mcrypt fping git pear install Net_IPv4-1.3.4 pear install Net_IPv6-1.2.2b2 ``` diff --git a/doc/Support/Features.md b/doc/Support/Features.md index dc2c24db3..2d994c4bc 100644 --- a/doc/Support/Features.md +++ b/doc/Support/Features.md @@ -1,109 +1,128 @@ -Here's a bref list of supported vendors, some might be missing. -If you are unsure of whether your device is supported or not, feel free to ask us. +### Features + +Here's a brief list of supported features, some might be missing. +If you think something is missing, feel free to ask us. + +* Alerting +* API +* Auto Updating +* Customizable Dashboards +* Device Backup (Oxidized, RANCID) +* Distributed Polling +* Multiple Authentication Methods (MySQL, LDAP, Active Directory, HTTP) +* NetFlow, sFlow, IPFIX (NfSen) +* Service monitoring (Nagios Plugins) +* Syslog (Integrated, Graylog) +* Traffic Billing (Quota, 95th Percentile) +* Two Factor Authentication ### Vendors +Here's a brief list of supported vendors, some might be missing. +If you are unsure of whether your device is supported or not, feel free to ask us. -3Com -Aerohive -AKCP -Alcatel-Lucent -Allied Telesis -APC -Apple -Areca -Arista -Aruba -Avaya -Avocent -Axis -Barracuda -BCM963 -BNT -Brocade -Brother -Canopy -Cisco -CiscoSB -Citrix -Cometsystem -Comware -D-Link -Datacom -Dell -Delta Power Solutions -Eaton -Engenius -Enterasys -Epson -Extreme Networks -F5 -FiberHome -Force10 -Fortigate -FreeBSD -Gamatronic -Hikvision -HP -Huawei -IBM -iPoMan -ITWatchDogs -Juniper -Konica Minolta -Kyocera -Liebert -Linux -Mellanox -Meraki -MGE -Mikrotic -MRVLD -Multimatic -NetApp -NetBSD -Netgear -NetMan -Netonix -Netopia -NetVision -NetWare -NRG -OKI -OpenBSD -PacketShaper -Palo Alto Networks -Papouch -PBN -Perle -Powercode -Prestige -Proxim -Proxmox -Radlan -Raritan -Redback -Ricoh -Riverbed -Ruckus -SAF -Siklu -Sentry3 -Solaris -SonicWALL -SpeedTouch -Supermicro -Symbol -TPLink -Tranzeo -Triplite -Ubiquiti -VMware -VRP -Vyatta -VyOS -Watchguard -WebPower -Windows -Xerox -ZTE -Zyxel +* 3Com +* Aerohive +* AKCP +* Alcatel-Lucent +* Allied Telesis +* APC +* Apple +* Areca +* Arista +* Aruba +* Avaya +* Avocent +* Axis +* Barracuda +* BCM963 +* BNT +* Brocade +* Brother +* Canopy +* Cisco +* Cisco Small Business +* Citrix +* Cometsystem +* Comware +* D-Link +* Datacom +* Dell +* Delta Power Solutions +* Eaton +* Engenius +* Enterasys +* Epson +* Extreme Networks +* F5 +* FiberHome +* Force10 +* Fortigate +* FreeBSD +* Gamatronic +* Hikvision +* HP +* Huawei +* IBM +* iPoMan +* ITWatchDogs +* Juniper +* Konica Minolta +* Kyocera +* Liebert +* LigoWave +* Linux +* Mellanox +* Meraki +* MGE +* Mikrotic +* MRVLD +* Multimatic +* NetApp +* NetBSD +* NETGEAR +* NetMan +* Netonix +* Netopia +* NetVision +* NetWare +* NRG +* OKI +* OpenBSD +* PacketShaper +* Palo Alto Networks +* Papouch +* PBN +* Perle +* Powercode +* Prestige +* Proxim +* Proxmox +* Quanta +* Radlan +* Raritan +* Redback +* Ricoh +* Riverbed +* Ruckus +* SAF +* Siklu +* Sentry3 +* Solaris +* SonicWALL +* SpeedTouch +* Supermicro +* Symbol +* TPLink +* Tranzeo +* Triplite +* Ubiquiti +* VMware +* VRP +* Vyatta +* VyOS +* Watchguard +* WebPower +* Windows +* Xerox +* ZTE +* ZyXEL diff --git a/html/ajax_setresolution.php b/html/ajax_setresolution.php new file mode 100644 index 000000000..b72bd748e --- /dev/null +++ b/html/ajax_setresolution.php @@ -0,0 +1,8 @@ +li>a.dropdown-toggle { + padding: 15px 6px; +} + +@media only screen and (max-width: 480px) { + .thumbnail_graph_table b { font-size : 6px;} + .thumbnail_graph_table img { + max-width: 45px; + max-height: 50px; + } + .device-header-table .device_icon img { + max-width: 20px; + max-height: 20px; + } + .device-header-table img { + max-width: 115px; + max-height: 40px; + } + .device-header-table {font-size : 8px;} + .device-header-table a {font-size : 11px;} +} + +@media only screen and (max-width: 768px) and (min-width: 481px) { + .thumbnail_graph_table b { font-size : 8px;} + .thumbnail_graph_table img { + max-width: 60px; + max-height: 55px; + } + .device-header-table img { + max-width: 150px; + max-height: 50px; + } + .device-header-table {font-size : 10px;} + .device-header-table a {font-size : 17px;} +} + + +@media only screen and (max-width: 800px) and (min-width: 721px) { + .thumbnail_graph_table b { font-size : 8px;} + .thumbnail_graph_table img { + max-width: 75px; + max-height: 60px; + } +} + +@media only screen and (max-width: 1024px) and (min-width: 801px) { + .thumbnail_graph_table b { font-size : 9px;} + .thumbnail_graph_table img { + max-width: 105px; + max-height: 70px; + } +} + +@media only screen and (min-width: 1024px) { +} diff --git a/html/images/os/quanta.png b/html/images/os/quanta.png new file mode 100644 index 000000000..f3cb0db4a Binary files /dev/null and b/html/images/os/quanta.png differ diff --git a/html/includes/authentication/active_directory.inc.php b/html/includes/authentication/active_directory.inc.php index a1dd80f86..633ac0c04 100644 --- a/html/includes/authentication/active_directory.inc.php +++ b/html/includes/authentication/active_directory.inc.php @@ -86,7 +86,16 @@ function auth_usermanagement() { function adduser($username) { // Check to see if user is already added in the database if (!user_exists_in_db($username)) { - return dbInsert(array('username' => $username, 'user_id' => get_userid($username), 'level' => "0", 'can_modify_passwd' => 0, 'twofactor' => 0), 'users'); + $userid = dbInsert(array('username' => $username, 'user_id' => get_userid($username), 'level' => "0", 'can_modify_passwd' => 0, 'twofactor' => 0), 'users'); + if ($userid == false) { + return false; + } + else { + foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc',array($userid)) as $notif) { + dbInsert(array('notifications_id'=>$notif['notifications_id'],'user_id'=>$userid,'key'=>'read','value'=>1),'notifications_attribs'); + } + } + return $userid; } else { return false; diff --git a/html/includes/authentication/http-auth.inc.php b/html/includes/authentication/http-auth.inc.php index 221de3adc..691480181 100644 --- a/html/includes/authentication/http-auth.inc.php +++ b/html/includes/authentication/http-auth.inc.php @@ -44,11 +44,20 @@ function auth_usermanagement() { } -function adduser($username, $password, $level, $email='', $realname='', $can_modify_passwd='1') { +function adduser($username, $password, $level, $email='', $realname='', $can_modify_passwd=1, $description='', $twofactor=0) { if (!user_exists($username)) { $hasher = new PasswordHash(8, false); $encrypted = $hasher->HashPassword($password); - return dbInsert(array('username' => $username, 'password' => $encrypted, 'level' => $level, 'email' => $email, 'realname' => $realname), 'users'); + $userid = dbInsert(array('username' => $username, 'password' => $encrypted, 'level' => $level, 'email' => $email, 'realname' => $realname, 'can_modify_passwd' => $can_modify_passwd, 'descr' => $description, 'twofactor' => $twofactor), 'users'); + if ($userid == false) { + return false; + } + else { + foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc',array($userid)) as $notif) { + dbInsert(array('notifications_id'=>$notif['notifications_id'],'user_id'=>$userid,'key'=>'read','value'=>1),'notifications_attribs'); + } + } + return $userid; } else { return false; diff --git a/html/includes/authentication/mysql.inc.php b/html/includes/authentication/mysql.inc.php index c16e716f8..0248d66b3 100644 --- a/html/includes/authentication/mysql.inc.php +++ b/html/includes/authentication/mysql.inc.php @@ -104,7 +104,16 @@ function adduser($username, $password, $level, $email='', $realname='', $can_mod if (!user_exists($username)) { $hasher = new PasswordHash(8, false); $encrypted = $hasher->HashPassword($password); - return dbInsert(array('username' => $username, 'password' => $encrypted, 'level' => $level, 'email' => $email, 'realname' => $realname, 'can_modify_passwd' => $can_modify_passwd, 'descr' => $description, 'twofactor' => $twofactor), 'users'); + $userid = dbInsert(array('username' => $username, 'password' => $encrypted, 'level' => $level, 'email' => $email, 'realname' => $realname, 'can_modify_passwd' => $can_modify_passwd, 'descr' => $description, 'twofactor' => $twofactor), 'users'); + if ($userid == false) { + return false; + } + else { + foreach (dbFetchRows('select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc',array($userid)) as $notif) { + dbInsert(array('notifications_id'=>$notif['notifications_id'],'user_id'=>$userid,'key'=>'read','value'=>1),'notifications_attribs'); + } + } + return $userid; } else { return false; diff --git a/html/includes/common/worldmap.inc.php b/html/includes/common/worldmap.inc.php index bf9e6f36e..cceb080b9 100644 --- a/html/includes/common/worldmap.inc.php +++ b/html/includes/common/worldmap.inc.php @@ -30,7 +30,7 @@ if ($config['map']['engine'] == 'leaflet') {
-
+
@@ -46,7 +46,7 @@ if ($config['map']['engine'] == 'leaflet') {
-
+
+
+
+ +
+
+ -
- Delete Bill -
- -
-
+
+
+
diff --git a/html/pages/bill/reset.inc.php b/html/pages/bill/reset.inc.php index 756f85bd2..5721cb9a5 100644 --- a/html/pages/bill/reset.inc.php +++ b/html/pages/bill/reset.inc.php @@ -1,5 +1,4 @@
- -
- Reset Bill -
- -
-
+
+
+
- + \ No newline at end of file diff --git a/html/pages/device.inc.php b/html/pages/device.inc.php index 688a18900..1a68f9dbf 100644 --- a/html/pages/device.inc.php +++ b/html/pages/device.inc.php @@ -33,7 +33,7 @@ if (device_permitted($vars['device']) || $check_device == $vars['device']) { } echo '
'; - echo ''; + echo '
'; require 'includes/device-header.inc.php'; echo '
'; echo '
'; diff --git a/html/pages/device/overview/processors.inc.php b/html/pages/device/overview/processors.inc.php index 4f631567a..3aae5f5f8 100644 --- a/html/pages/device/overview/processors.inc.php +++ b/html/pages/device/overview/processors.inc.php @@ -1,7 +1,5 @@ '; + $graph_array = array(); + $graph_array['to'] = $config['time']['now']; + $graph_array['type'] = 'processor_usage'; + $graph_array['from'] = $config['time']['day']; + $graph_array['legend'] = 'no'; + + $totalPercent=0; + foreach ($processors as $proc) { $text_descr = rewrite_entity_descr($proc['processor_descr']); - // disable short hrDeviceDescr. need to make this prettier. - // $text_descr = short_hrDeviceDescr($proc['processor_descr']); $percent = $proc['processor_usage']; - $background = get_percentage_colours($percent); - $graph_colour = str_replace('#', '', $row_colour); + if ($config['cpu_details_overview'] === true) + { - $graph_array = array(); + $background = get_percentage_colours($percent); + + $graph_array['id'] = $proc['processor_id']; + + //Generate tooltip graphs + $graph_array['height'] = '100'; + $graph_array['width'] = '210'; + $link_array = $graph_array; + $link_array['page'] = 'graphs'; + unset($link_array['height'], $link_array['width'], $link_array['legend']); + $link = generate_url($link_array); + $overlib_content = generate_overlib_content($graph_array, $device['hostname'].' - '.$text_descr); + + //Generate the minigraph + $graph_array['width'] = 80; + $graph_array['height'] = 20; + $graph_array['bg'] = 'ffffff00'; // the 00 at the end makes the area transparent. + $minigraph = generate_lazy_graph_tag($graph_array); + + echo ' + + + + '; + } + else { + $totalPercent = $totalPercent + $percent; + } + + }//end foreach + + if ($config['cpu_details_overview'] === false) + { + + //Generate average cpu graph $graph_array['height'] = '100'; - $graph_array['width'] = '210'; - $graph_array['to'] = $config['time']['now']; - $graph_array['id'] = $proc['processor_id']; - $graph_array['type'] = $graph_type; - $graph_array['from'] = $config['time']['day']; - $graph_array['legend'] = 'no'; + $graph_array['width'] = '485'; + $graph_array['device'] = $device['device_id']; + $graph_array['type'] = 'device_processor'; + $graph = generate_lazy_graph_tag($graph_array); + //Generate link to graphs $link_array = $graph_array; $link_array['page'] = 'graphs'; - unset($link_array['height'], $link_array['width'], $link_array['legend']); + unset($link_array['height'], $link_array['width']); $link = generate_url($link_array); - $overlib_content = generate_overlib_content($graph_array, $device['hostname'].' - '.$text_descr); - - $graph_array['width'] = 80; - $graph_array['height'] = 20; - $graph_array['bg'] = 'ffffff00'; - // the 00 at the end makes the area transparent. - $minigraph = generate_lazy_graph_tag($graph_array); + //Generate tooltip + $graph_array['width'] = '210'; + $overlib_content = generate_overlib_content($graph_array, $device['hostname'].' - CPU usage'); echo ' - - - - '; - }//end foreach + + '; + + //Add a row with CPU desc, count and percent graph + $totalPercent=$totalPercent/count($processors); + $background = get_percentage_colours($totalPercent); + + echo ' + + + + '; + + } echo '
'.overlib_link($link, $text_descr, $overlib_content).''.overlib_link($link, $minigraph, $overlib_content).''.overlib_link($link, print_percentage_bar(200, 20, $percent, null, 'ffffff', $background['left'], $percent.'%', 'ffffff', $background['right']), $overlib_content).' +
'.overlib_link($link, $text_descr, $overlib_content).''.overlib_link($link, $minigraph, $overlib_content).''.overlib_link($link, print_percentage_bar(200, 20, $percent, null, 'ffffff', $background['left'], $percent.'%', 'ffffff', $background['right']), $overlib_content).' -
'; + echo overlib_link($link, $graph, $overlib_content, null); + echo '
'.overlib_link($link, $text_descr, $overlib_content).''.overlib_link($link,'x'.count($processors),$overlib_content).''.overlib_link($link, print_percentage_bar(200, 20, $totalPercent, null, 'ffffff', $background['left'], $percent.'%', 'ffffff', $background['right']), $overlib_content).'
diff --git a/html/pages/device/ports.inc.php b/html/pages/device/ports.inc.php index 56d3ee542..c03812029 100644 --- a/html/pages/device/ports.inc.php +++ b/html/pages/device/ports.inc.php @@ -103,9 +103,11 @@ if ($vars['view'] == 'minigraphs') { echo "
\ +
\ +
".$device['hostname'].' - '.$port['ifDescr'].'
\ '.$port['ifAlias']." \ \ +
\ ', CENTER, LEFT, FGCOLOR, '#e5e5e5', BGCOLOR, '#e5e5e5', WIDTH, 400, HEIGHT, 150);\" onmouseout=\"return nd();\" >"."
".truncate(short_port_descr($port['ifAlias']), 32, '').'
@@ -121,8 +123,19 @@ else { if ($vars['view'] == 'details') { $port_details = 1; } +?> +
+ + + + + + + + + +
">Port">TrafficSpeedMediaMac Address
"; $i = '1'; global $port_cache, $port_index_cache; @@ -130,9 +143,20 @@ else { $ports = dbFetchRows("SELECT * FROM `ports` WHERE `device_id` = ? AND `deleted` = '0' ORDER BY `ifIndex` ASC", array($device['device_id'])); // As we've dragged the whole database, lets pre-populate our caches :) // FIXME - we should probably split the fetching of link/stack/etc into functions and cache them here too to cut down on single row queries. - foreach ($ports as $port) { + + foreach ($ports as $key => $port) { $port_cache[$port['port_id']] = $port; $port_index_cache[$port['device_id']][$port['ifIndex']] = $port; + $ports[$key]["ifOctets_rate"] = $port["ifInOctets_rate"] + $port["ifOutOctets_rate"]; + } + + switch ($vars["sort"]) { + case 'traffic': + $ports = array_sort($ports, 'ifOctets_rate', SORT_DESC); + break; + default: + $ports = array_sort($ports, 'ifIndex', SORT_ASC); + break; } foreach ($ports as $port) { diff --git a/html/pages/device/showconfig.inc.php b/html/pages/device/showconfig.inc.php index ac5672d82..675e43674 100644 --- a/html/pages/device/showconfig.inc.php +++ b/html/pages/device/showconfig.inc.php @@ -98,13 +98,13 @@ if ($_SESSION['userlevel'] >= '7') { list($oid,$date,$version) = explode('|',mres($_POST['config'])); $text = file_get_contents($config['oxidized']['url'].'/node/version/view?node='.$device['hostname'].'&group=&oid='.$oid.'&date='.urlencode($date).'&num='.$version.'&format=text'); if ($text == 'node not found') { - $text = file_get_contents($config['oxidized']['url'].'/node/version/view?node='.$device['hostname'].'&group='.$device['os'].'&oid='.$oid.'&date='.urlencode($date).'&num='.$version.'&format=text'); + $text = file_get_contents($config['oxidized']['url'].'/node/version/view?node='.$device['hostname'].'&group='.(is_array($node_info) ? $node_info['group'] : $device['os']).'&oid='.$oid.'&date='.urlencode($date).'&num='.$version.'&format=text'); } } else { $text = file_get_contents($config['oxidized']['url'].'/node/fetch/'.$device['hostname']); if ($text == 'node not found') { - $text = file_get_contents($config['oxidized']['url'].'/node/fetch/'.$device['os'].'/'.$device['hostname']); + $text = file_get_contents($config['oxidized']['url'].'/node/fetch/'.(is_array($node_info) ? $node_info['group'] : $device['os']).'/'.$device['hostname']); } } if ($config['oxidized']['features']['versioning'] === true) { diff --git a/html/pages/front/tiles.php b/html/pages/front/tiles.php index 63c971135..f847d41c1 100644 --- a/html/pages/front/tiles.php +++ b/html/pages/front/tiles.php @@ -194,14 +194,10 @@ foreach (dbFetchRows("SELECT * FROM `widgets` ORDER BY `widget_title`") as $widg -
-
-
-
diff --git a/html/pages/ripenccapi.inc.php b/html/pages/ripenccapi.inc.php new file mode 100644 index 000000000..fbcfbd864 --- /dev/null +++ b/html/pages/ripenccapi.inc.php @@ -0,0 +1,66 @@ + + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + */ +$pagetitle[] = 'RIPE NCC - API Tools'; +?> +

RIPE NCC API Tools

+
+ +
+ +
+
+ +
+
+
+ + + + +
+ +
+ +
+ diff --git a/html/pages/routing/bgp.inc.php b/html/pages/routing/bgp.inc.php index 3268715ec..d70555c03 100644 --- a/html/pages/routing/bgp.inc.php +++ b/html/pages/routing/bgp.inc.php @@ -266,7 +266,7 @@ else { $peer_ip = $peer['bgpLocalAddr']; } - $localaddresslink = "', LEFT".$config['overlib_defaults'].');" onmouseout="return nd();">'.$peer_ip.''; + $localaddresslink = "', LEFT".$config['overlib_defaults'].');" onmouseout="return nd();">'.$peer_ip.''; $graph_type = 'bgp_updates'; $peer_daily_url = 'graph.php?id='.$peer['bgpPeer_id'].'&type='.$graph_type.'&from='.$config['time']['day'].'&to='.$config['time']['now'].'&width=500&height=150'; @@ -277,7 +277,7 @@ else { $peer_ident = $peer['bgpPeerIdentifier']; } - $peeraddresslink = "', LEFT".$config['overlib_defaults'].');" onmouseout="return nd();">'.$peer_ident.''; + $peeraddresslink = "', LEFT".$config['overlib_defaults'].');" onmouseout="return nd();">'.$peer_ident.''; echo ''; diff --git a/html/pages/settings/alerting.inc.php b/html/pages/settings/alerting.inc.php index 9c763811d..df70e071e 100644 --- a/html/pages/settings/alerting.inc.php +++ b/html/pages/settings/alerting.inc.php @@ -720,6 +720,116 @@ echo '
+
+
+

+ VictorOps +

+
+
+
+
+ +
+
+ + +
+
+
+
+
'; + +$clickatell = get_config_by_name('alert.transports.clickatell.token'); +$mobiles = get_config_like_name('alert.transports.clickatell.to.%'); +$new_mobiles = array(); +foreach ($mobiles as $mobile) { + $new_mobiles[] = $mobile['config_value']; +} +$upd_mobiles = implode(PHP_EOL, $new_mobiles); + +echo ' +
+
+

+ Clickatell transport +

+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
'; +$playsms_url = get_config_by_name('alert.transports.playsms.url'); +$playsms_user = get_config_by_name('alert.transports.playsms.user'); +$playsms_token = get_config_by_name('alert.transports.playsms.token'); +$playsms_from = get_config_by_name('alert.transports.playsms.from'); +$mobiles = get_config_like_name('alert.transports.playsms.to.%'); +$new_mobiles = array(); +foreach ($mobiles as $mobile) { + $new_mobiles[] = $mobile['config_value']; +} +$upd_mobiles = implode(PHP_EOL, $new_mobiles); +echo ' +
+
+

+ PlaySMS transport +

+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
'; diff --git a/includes/alerts/transport.clickatell.php b/includes/alerts/transport.clickatell.php new file mode 100644 index 000000000..02d1f5a91 --- /dev/null +++ b/includes/alerts/transport.clickatell.php @@ -0,0 +1,42 @@ +/* Copyright (C) 2015 Daniel Preussker + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +/** + * Clickatell REST-API Transport + * @author f0o + * @copyright 2015 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +$data = array("api_id" => $opts['api_id'], "user" => $opts['user'], "password" => $opts['password'], "to" => implode(',',$opts['to']), "text" => $obj['title']); +if (!empty($opts['from'])) { + $data['from'] = $opts['from']; +} +$url = 'https://api.clickatell.com/http/sendmsg?'.http_build_query($data); +$curl = curl_init($url); + +curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST"); +curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + +$ret = curl_exec($curl); +$code = curl_getinfo($curl, CURLINFO_HTTP_CODE); +if( $code > 200 ) { + if( $debug ) { + var_dump($ret); + } + return false; +} +return true; diff --git a/includes/alerts/transport.playsms.php b/includes/alerts/transport.playsms.php new file mode 100644 index 000000000..812ae4ffe --- /dev/null +++ b/includes/alerts/transport.playsms.php @@ -0,0 +1,42 @@ +/* Copyright (C) 2015 Daniel Preussker + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +/** + * PlaySMS API Transport + * @author f0o + * @copyright 2015 f0o, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +$data = array("u" => $opts['user'], "h" => $opts['token'], "to" => implode(',',$opts['to']), "msg" => $obj['title']); +if (!empty($opts['from'])) { + $data["from"] = $opts['from']; +} +$url = $opts['url'].'&op=pv&'.http_build_query($data); +$curl = curl_init($url); + +curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET"); +curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + +$ret = curl_exec($curl); +$code = curl_getinfo($curl, CURLINFO_HTTP_CODE); +if( $code > 202 ) { + if( $debug ) { + var_dump($ret); + } + return false; +} +return true; diff --git a/includes/alerts/transport.victorops.php b/includes/alerts/transport.victorops.php new file mode 100644 index 000000000..7e1fd622a --- /dev/null +++ b/includes/alerts/transport.victorops.php @@ -0,0 +1,57 @@ +/* Copyright (C) 2015 Daniel Preussker + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +/** + * VictorOps Generic-API Transport - Based on PagerDuty transport + * @author f0o + * @author laf + * @copyright 2015 f0o, laf, LibreNMS + * @license GPL + * @package LibreNMS + * @subpackage Alerts + */ + +$url = $opts['url']; + +$protocol = array( + 'entity_id' => ($obj['id'] ? $obj['id'] : $obj['uid']), + 'state_start_time' => strtotime($obj['timestamp']), + 'monitoring_tool' => 'librenms', +); +if( $obj['state'] == 0 ) { + $protocol['message_type'] = 'recovery'; +} +elseif( $obj['state'] == 2 ) { + $protocol['message_type'] = 'acknowledgement'; +} +elseif ($obj['state'] == 1) { + $protocol['message_type'] = 'critical'; +} + +foreach( $obj['faults'] as $fault=>$data ) { + $protocol['state_message'] .= $data['string']; +} + +$curl = curl_init(); +curl_setopt($curl, CURLOPT_URL, $url ); +curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type'=> 'application/json')); +curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($protocol)); +$ret = curl_exec($curl); +$code = curl_getinfo($curl, CURLINFO_HTTP_CODE); +if( $code != 200 ) { + var_dump("VictorOps returned Error, retry later"); //FIXME: propper debuging + return false; +} +return true; diff --git a/includes/caches/notifications.inc.php b/includes/caches/notifications.inc.php new file mode 100644 index 000000000..0716010a4 --- /dev/null +++ b/includes/caches/notifications.inc.php @@ -0,0 +1,47 @@ + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +/** + * Notification Cache + * @author Daniel Preussker + * @copyright 2015 Daniel Preussker, QuxLabs UG + * @license GPL + * @package LibreNMS + * @subpackage Notifications + */ + +$data['count'] = array( + 'query' => 'select count(notifications.notifications_id) from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?)', + 'params' => array( $_SESSION['user_id'] ) +); + +$data['unread'] = array( + 'query' => 'select notifications.* from notifications where not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.user_id = ?) order by notifications.notifications_id desc', + 'params' => array( $_SESSION['user_id'] ) +); + +$data['sticky'] = array( + 'query' => 'select notifications.*,notifications_attribs.user_id from notifications inner join notifications_attribs on notifications.notifications_id = notifications_attribs.notifications_id where notifications_attribs.key = "sticky" && notifications_attribs.value = 1 order by notifications_attribs.attrib_id desc', +); + +$data['sticky_count'] = array( + 'query' => 'select count(notifications.notifications_id) from notifications inner join notifications_attribs on notifications.notifications_id = notifications_attribs.notifications_id where notifications_attribs.key = "sticky" && notifications_attribs.value = 1', +); + +$data['read'] = array( + 'query' => 'select notifications.* from notifications inner join notifications_attribs on notifications.notifications_id = notifications_attribs.notifications_id where notifications_attribs.user_id = ? && ( notifications_attribs.key = "read" && notifications_attribs.value = 1) && not exists( select 1 from notifications_attribs where notifications.notifications_id = notifications_attribs.notifications_id and notifications_attribs.key = "sticky" && notifications_attribs.value = "1") order by notifications_attribs.attrib_id desc', + 'params' => array( $_SESSION['user_id'] ) +); + diff --git a/includes/common.php b/includes/common.php index 2f7a7aedc..de68cfd78 100644 --- a/includes/common.php +++ b/includes/common.php @@ -795,3 +795,15 @@ function ceph_rrd($gtype) { $rrd = join('-', array('app', 'ceph', $vars['id'], $gtype, $var)).'.rrd'; return join('/', array($config['rrd_dir'], $device['hostname'], $rrd)); } + +/** + * Parse location field for coordinates + * @param string location The location field to look for coords in. + * @return array Containing the lat and lng coords +**/ +function parse_location($location) { + preg_match('/(\[)(-?[0-9\. ]+),[ ]*(-?[0-9\. ]+)(\])/', $location, $tmp_loc); + if (!empty($tmp_loc[2]) && !empty($tmp_loc[3])) { + return array('lat' => $tmp_loc[2], 'lng' => $tmp_loc[3]); + } +}//end parse_location() diff --git a/includes/defaults.inc.php b/includes/defaults.inc.php index 1ea8f3e15..0e936861b 100644 --- a/includes/defaults.inc.php +++ b/includes/defaults.inc.php @@ -408,6 +408,8 @@ $config['network_map_vis_options'] = '{ // Device page options $config['show_overview_tab'] = true; +$config['cpu_details_overview'] = false; //By default show only average cpu in device overview + // The device overview page options $config['overview_show_sysDescr'] = true; @@ -825,3 +827,6 @@ $config['summary_errors'] = 0; // Default width of the availability map's tiles $config['availability-map-width'] = 25; + +// Default notifications Feed +$config['notifications']['LibreNMS'] = 'http://www.librenms.org/notifications.rss'; diff --git a/includes/definitions.inc.php b/includes/definitions.inc.php index 16873101c..56e4b28eb 100644 --- a/includes/definitions.inc.php +++ b/includes/definitions.inc.php @@ -107,6 +107,17 @@ $config['os'][$os]['over'][1]['text'] = 'Processor Usage'; $config['os'][$os]['over'][2]['graph'] = 'device_ucd_memory'; $config['os'][$os]['over'][2]['text'] = 'Memory Usage'; +$os = 'infinity'; +$config['os'][$os]['text'] = 'LigoWave Infinity'; +$config['os'][$os]['type'] = 'wireless'; +$config['os'][$os]['nobulk'] = 1; +$config['os'][$os]['over'][0]['graph'] = 'device_bits'; +$config['os'][$os]['over'][0]['text'] = 'Device Traffic'; +$config['os'][$os]['over'][1]['graph'] = 'device_processor'; +$config['os'][$os]['over'][1]['text'] = 'Processor Usage'; +$config['os'][$os]['over'][2]['graph'] = 'device_mempool'; +$config['os'][$os]['over'][2]['text'] = 'Memory Usage'; + // Ubiquiti $os = 'unifi'; $config['os'][$os]['text'] = 'Ubiquiti UniFi'; @@ -517,6 +528,14 @@ $config['os'][$os]['bad_if'][] = 'cpu'; $config['os'][$os]['over'][0]['graph'] = 'device_bits'; $config['os'][$os]['over'][0]['text'] = 'Device Traffic'; +//Quanta switches +$os = 'quanta'; +$config['os'][$os]['text'] = 'Quanta'; +$config['os'][$os]['type'] = 'network'; +$config['os'][$os]['icon'] = 'quanta'; +$config['os'][$os]['over'][0]['graph'] = 'device_bits'; +$config['os'][$os]['over'][0]['text'] = 'Device Traffic'; + $os = 'netonix'; $config['os'][$os]['text'] = 'Netonix'; $config['os'][$os]['type'] = 'network'; @@ -1229,6 +1248,8 @@ $os = 'canopy'; $config['os'][$os]['text'] = 'Cambium'; $config['os'][$os]['type'] = 'wireless'; $config['os'][$os]['icon'] = 'cambium'; +$config['os'][$os]['over'][0]['graph'] = 'device_bits'; +$config['os'][$os]['over'][0]['text'] = 'Device Traffic'; $os = 'datacom'; $config['os'][$os]['text'] = 'Datacom'; diff --git a/includes/device-groups.inc.php b/includes/device-groups.inc.php index a1573ec03..45d5afcd2 100644 --- a/includes/device-groups.inc.php +++ b/includes/device-groups.inc.php @@ -47,6 +47,9 @@ function GenGroupSQL($pattern, $search='') { } } + $pattern = rtrim($pattern, '&&'); + $pattern = rtrim($pattern, '||'); + $tables = array_keys(array_flip($tables)); $x = sizeof($tables); $i = 0; diff --git a/includes/discovery/entity-physical.inc.php b/includes/discovery/entity-physical.inc.php index 812be65ff..caacb5be2 100644 --- a/includes/discovery/entity-physical.inc.php +++ b/includes/discovery/entity-physical.inc.php @@ -37,6 +37,9 @@ if ($config['enable_inventory']) { $entPhysicalIsFRU = $entry['jnxFruType']; $entPhysicalAlias = $entry['entPhysicalAlias']; $entPhysicalAssetID = $entry['entPhysicalAssetID']; + // fix for issue 1865, $entPhysicalIndex, as it contains a quad dotted number on newer Junipers + // using str_replace to remove all dots should fix this even if it changes in future + $entPhysicalIndex = str_replace('.','',$entPhysicalIndex); } else { $entPhysicalDescr = $entry['entPhysicalDescr']; @@ -127,18 +130,7 @@ if ($config['enable_inventory']) { echo '+'; }//end if - if ($device['os'] == 'junos') { - // $entPhysicalIndex appears as a numeric OID fragment - // (string), so convert it to an "integer" for the - // validation step below since it is stored in the DB as - // an integer. This should be fixed. - list($first,$second) = explode('.', $entPhysicalIndex); - $entPhysicalIndexNoDots = $first.$second; - $valid[$entPhysicalIndexNoDots] = 1; - } - else { - $valid[$entPhysicalIndex] = 1; - } + $valid[$entPhysicalIndex] = 1; }//end if }//end foreach } diff --git a/includes/discovery/functions.inc.php b/includes/discovery/functions.inc.php index 3fefbd745..a90a65d41 100644 --- a/includes/discovery/functions.inc.php +++ b/includes/discovery/functions.inc.php @@ -34,7 +34,8 @@ function discover_new_device($hostname, $device='', $method='', $interface='') { return false; } } - else { + elseif (filter_var($dst_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === true || filter_var($dst_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === true){ + // gethostbyname returned a valid $ip, was $dst_host an IP? if ($config['discovery_by_ip'] === false) { d_echo('Discovery by IP disabled, skipping ' . $dst_host); return false; diff --git a/includes/discovery/mempools/infinity.inc.php b/includes/discovery/mempools/infinity.inc.php new file mode 100644 index 000000000..a6734cb31 --- /dev/null +++ b/includes/discovery/mempools/infinity.inc.php @@ -0,0 +1,22 @@ + + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + */ + +if ($device['os'] == 'infinity') { + echo 'INFINITY-MEMORY-POOL: '; + + $total = snmp_get($device, '.1.3.6.1.4.1.10002.1.1.1.1.1.0', '-OvQ'); + $free = snmp_get($device, '.1.3.6.1.4.1.10002.1.1.1.1.2.0', '-OvQ'); + + if (is_numeric($total) && is_numeric($free)) { + discover_mempool($valid_mempool, $device, 0, 'infinity', 'Memory', '1', null, null); + } +} diff --git a/includes/discovery/os/edgeswitch.inc.php b/includes/discovery/os/edgeswitch.inc.php index c3cf06bee..c09f86dc2 100644 --- a/includes/discovery/os/edgeswitch.inc.php +++ b/includes/discovery/os/edgeswitch.inc.php @@ -1,6 +1,6 @@ + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + */ + +if (!$os) { + if (preg_match('/^NFT 2N/', $sysDescr)) { + $os = 'infinity'; + } +} diff --git a/includes/discovery/os/netonix.inc.php b/includes/discovery/os/netonix.inc.php index 04f977230..46ee5b89e 100644 --- a/includes/discovery/os/netonix.inc.php +++ b/includes/discovery/os/netonix.inc.php @@ -1,7 +1,6 @@ + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + */ + +if ($device['os'] == 'infinity') { + echo 'LigoWave Infinity: '; + + $descr = 'CPU'; + $usage = snmp_get($device, '.1.3.6.1.4.1.10002.1.1.1.4.2.1.3.2', '-Ovqn'); + + if (is_numeric($usage)) { + discover_processor($valid['processor'], $device, '.1.3.6.1.4.1.10002.1.1.1.4.2.1.3.2', '0', 'infinity', $descr, '1', $usage, null, null); + } +} + +unset($processors_array); diff --git a/includes/functions.php b/includes/functions.php index 11ff1531f..318656fc1 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1278,3 +1278,31 @@ function host_exists($hostname) { return false; } } + +/** + * Check the innodb buffer size + * + * @return array including the current set size and the currently used buffer +**/ +function innodb_buffer_check() { + $pool['size'] = dbFetchCell('SELECT @@innodb_buffer_pool_size'); + // The following query is from the excellent mysqltuner.pl by Major Hayden https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl + $pool['used'] = dbFetchCell('SELECT SUM(DATA_LENGTH+INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ("information_schema", "performance_schema", "mysql") AND ENGINE = "InnoDB" GROUP BY ENGINE ORDER BY ENGINE ASC'); + return $pool; +} + +/** + * Print warning about InnoDB buffer size + * + * @param array $innodb_buffer An array that contains the used and current size + * + * @return string $output +**/ +function warn_innodb_buffer($innodb_buffer) { + $output = 'InnoDB Buffersize too small.'.PHP_EOL; + $output .= 'Current size: '.($innodb_buffer['size'] / 1024 / 1024).' MiB'.PHP_EOL; + $output .= 'Minimum Required: '.($innodb_buffer['used'] / 1024 / 1024).' MiB'.PHP_EOL; + $output .= 'To ensure integrity, we\'re not going to pull any updates until the buffersize has been adjusted.'.PHP_EOL; + $output .= 'Config proposal: "innodb_buffer_pool_size = '.pow(2,ceil(log(($innodb_buffer['used'] / 1024 / 1024),2))).'M"'.PHP_EOL; + return $output; +} diff --git a/includes/object-cache.inc.php b/includes/object-cache.inc.php index 61cea5323..dfb81621e 100644 --- a/includes/object-cache.inc.php +++ b/includes/object-cache.inc.php @@ -95,7 +95,10 @@ class ObjCache implements ArrayAccess { return $GLOBALS['_ObjCache'][$this->obj][$obj]['value']; } else { - $GLOBALS['_ObjCache'][$this->obj][$obj]['value'] = dbFetchCell($this->data[$obj]['query'], $this->data[$obj]['params']); + $GLOBALS['_ObjCache'][$this->obj][$obj]['value'] = dbFetchRows($this->data[$obj]['query'], $this->data[$obj]['params']); + if (sizeof($GLOBALS['_ObjCache'][$this->obj][$obj]['value']) == 1 && sizeof($GLOBALS['_ObjCache'][$this->obj][$obj]['value'][0]) == 1) { + $GLOBALS['_ObjCache'][$this->obj][$obj]['value'] = current($GLOBALS['_ObjCache'][$this->obj][$obj]['value'][0]); + } return $GLOBALS['_ObjCache'][$this->obj][$obj]['value']; } } diff --git a/includes/polling/applications/powerdns.inc.php b/includes/polling/applications/powerdns.inc.php index 3a7c728c2..0ffca6843 100644 --- a/includes/polling/applications/powerdns.inc.php +++ b/includes/polling/applications/powerdns.inc.php @@ -6,7 +6,12 @@ $options = '-O qv'; $mib = 'NET-SNMP-EXTEND-MIB'; $oid = 'nsExtendOutputFull.8.112.111.119.101.114.100.110.115'; -$powerdns = snmp_get($device, $oid, $options, $mib); +if ($agent_data['app']['powerdns']) { + $powerdns = $agent_data['app']['powerdns']; +} +else { + $powerdns = snmp_get($device, $oid, $options, $mib); +} echo ' powerdns'; diff --git a/includes/polling/applications/proxmox.inc.php b/includes/polling/applications/proxmox.inc.php index 37f8d8cd4..8e5dc0b90 100644 --- a/includes/polling/applications/proxmox.inc.php +++ b/includes/polling/applications/proxmox.inc.php @@ -41,16 +41,16 @@ function proxmox_vm_exists($i, $c, &$pmxcache) { $pmxlines = explode("\n", $proxmox); -if (count($pmxlines) > 2) { - $pmxcluster = array_shift($pmxlines); +$pmxcluster = array_shift($pmxlines); - $pmxcdir = join('/', array($config['rrd_dir'],'proxmox',$pmxcluster)); - if (!is_dir($pmxcdir)) { - mkdir($pmxcdir, 0775, true); - } +$pmxcdir = join('/', array($config['rrd_dir'],'proxmox',$pmxcluster)); +if (!is_dir($pmxcdir)) { + mkdir($pmxcdir, 0775, true); +} - dbUpdate(array('device_id' => $device['device_id'], 'app_type' => 'proxmox', 'app_instance' => $pmxcluster), 'applications', '`device_id` = ? AND `app_type` = ?', array($device['device_id'], 'proxmox')); +dbUpdate(array('device_id' => $device['device_id'], 'app_type' => 'proxmox', 'app_instance' => $pmxcluster), 'applications', '`device_id` = ? AND `app_type` = ?', array($device['device_id'], 'proxmox')); +if (count($pmxlines) > 0) { $pmxcache = []; foreach ($pmxlines as $vm) { diff --git a/includes/polling/functions.inc.php b/includes/polling/functions.inc.php index fff7bbe45..72332250e 100644 --- a/includes/polling/functions.inc.php +++ b/includes/polling/functions.inc.php @@ -445,7 +445,10 @@ function location_to_latlng($device) { if (!empty($device_location)) { $new_device_location = preg_replace("/ /","+",$device_location); // We have a location string for the device. - $loc = dbFetchRow("SELECT `lat`,`lng` FROM `locations` WHERE `location`=? LIMIT 1", array($device_location)); + $loc = parse_location($device_location); + if (!is_array($loc)) { + $loc = dbFetchRow("SELECT `lat`,`lng` FROM `locations` WHERE `location`=? LIMIT 1", array($device_location)); + } if (is_array($loc) === false) { // Grab data from which ever Geocode service we use. switch ($config['geoloc']['engine']) { diff --git a/includes/polling/mempools/infinity.inc.php b/includes/polling/mempools/infinity.inc.php new file mode 100644 index 000000000..94e3e9dd1 --- /dev/null +++ b/includes/polling/mempools/infinity.inc.php @@ -0,0 +1,17 @@ + + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + */ + +$total = snmp_get($device, '.1.3.6.1.4.1.10002.1.1.1.1.1.0', '-OvQ'); +$free = snmp_get($device, '.1.3.6.1.4.1.10002.1.1.1.1.2.0', '-OvQ'); +$mempool['total'] = $total * 1024; +$mempool['free'] = $free * 1024; +$mempool['used'] = ($mempool['total'] - $mempool['free']); diff --git a/includes/polling/os/infinity.inc.php b/includes/polling/os/infinity.inc.php new file mode 100644 index 000000000..ddebce091 --- /dev/null +++ b/includes/polling/os/infinity.inc.php @@ -0,0 +1,15 @@ + + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + */ + +$version = snmp_get($device, 'SNMPv2-MIB::sysDescr.0', '-Ovq'); +$version = explode(' ', $version)[2]; +$hardware = snmp_get($device, 'IEEE802dot11-MIB::dot11manufacturerProductName.5', '-Ovq'); diff --git a/includes/polling/os/quanta.inc.php b/includes/polling/os/quanta.inc.php new file mode 100644 index 000000000..60e71cde1 --- /dev/null +++ b/includes/polling/os/quanta.inc.php @@ -0,0 +1,3 @@ + $device['device_id'], 'app_type' => $key, 'app_status' => '', 'app_instance' => ''), 'applications'); diff --git a/lib/gridster/.gitignore b/lib/gridster/.gitignore index 440a178da..c5f40aab3 100644 --- a/lib/gridster/.gitignore +++ b/lib/gridster/.gitignore @@ -4,4 +4,8 @@ gh-pages/ demo/ .idea .DS_Store -.idea +*.iml +vendor +*.gem +*.css.map +demos/assets/css/demo.css diff --git a/lib/gridster/.jshintrc b/lib/gridster/.jshintrc new file mode 100644 index 000000000..9c9093da7 --- /dev/null +++ b/lib/gridster/.jshintrc @@ -0,0 +1,103 @@ +{ + // http://www.jshint.com/docs/ + // Based on node-jshint@2.x.x + + // ENFORCING OPTIONS + // These options tell JSHint to be more strict towards your code. Use them if + // you want to allow only a safe subset of JavaScript—very useful when your + // codebase is shared with a big number of developers with different skill + // levels. + + "bitwise": true, //prohibits the use of bitwise operators such as ^ (XOR), | (OR) and others + "camelcase": false, //force all variable names to use either camelCase style or UPPER_CASE with underscores + "curly": true, //requires you to always put curly braces around blocks in loops and conditionals + "eqeqeq": true, //prohibits the use of == and != in favor of === and !== + "es3": false, //tells JSHint that your code needs to adhere to ECMAScript 3 specification + "forin": false, //requires all `for in` loops to filter object's items with `hasOwnProperty()` + "immed": true, //prohibits the use of immediate function invocations without wrapping them in parentheses + "indent": 2, //enforces specific tab width + "latedef": true, //prohibits the use of a variable before it was defined + "newcap": true, //requires you to capitalize names of constructor functions + "noarg": true, //prohibits the use of `arguments.caller` and `arguments.callee` + "noempty": true, //warns when you have an empty block in your code + "nonew": true, //prohibits the use of constructor functions for side-effects + "plusplus": false, //prohibits the use of unary increment and decrement operators + "quotmark": true, //enforces the consistency of quotation marks used throughout your code + "undef": true, //prohibits the use of explicitly undeclared variables + "unused": "vars", //warns when you define and never use your variables + "strict": true, //requires all functions to run in ECMAScript 5's strict mode + "trailing": true, //makes it an error to leave a trailing whitespace in your code + "maxparams": false, //set the max number of formal parameters allowed per function + "maxdepth": 6, //control how nested do you want your blocks to be + "maxstatements": false, //set the max number of statements allowed per function + "maxcomplexity": false, //control cyclomatic complexity throughout your code + + // RELAXING OPTIONS + // These options allow you to suppress certain types of warnings. Use them + // only if you are absolutely positive that you know what you are doing. + + "asi": false, //suppresses warnings about missing semicolons + "boss": false, //suppresses warnings about the use of assignments in cases where comparisons are expected + "debug": false, //suppresses warnings about the debugger statements in your code + "eqnull": false, //suppresses warnings about == null comparisons + "esnext": false, //your code uses ES.next specific features such as const + "evil": false, //suppresses warnings about the use of eval + "expr": true, //suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls + "funcscope": false, //suppresses warnings about declaring variables inside of control structures while accessing them later from the outside + "globalstrict": false, //suppresses warnings about the use of global strict mode + "iterator": false, //suppresses warnings about the `__iterator__` property + "lastsemic": false, //suppresses warnings about missing semicolons, but only when the semicolon is omitted for the last statement in a one-line block + "laxbreak": false, //suppresses most of the warnings about possibly unsafe line breakings in your code + "laxcomma": false, //suppresses warnings about comma-first coding style + "loopfunc": false, //suppresses warnings about functions inside of loops + "moz": false, //tells JSHint that your code uses Mozilla JavaScript extensions + "multistr": false, //suppresses warnings about multi-line strings + "proto": false, //suppresses warnings about the `__proto__` property + "scripturl": false, //suppresses warnings about the use of script-targeted URLs—such as `javascript:...` + "smarttabs": false, //suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only + "shadow": false, //suppresses warnings about variable shadowing + "sub": false, //suppresses warnings about using `[]` notation when it can be expressed in dot notation + "supernew": false, //suppresses warnings about "weird" constructions like `new function () { ... }` and `new Object;` + "validthis": false, //suppresses warnings about possible strict violations when the code is running in strict mode and you use `this` in a non-constructor function + + // ENVIRONMENTS + // These options pre-define global variables that are exposed by popular + // JavaScript libraries and runtime environments—such as browser or node.js. + // Essentially they are shortcuts for explicit declarations like + // /*global $:false, jQuery:false */ + + "browser": true, //defines globals exposed by modern browsers + "couch": false, //defines globals exposed by CouchDB + "devel": true, //defines globals that are usually used for logging poor-man's debugging: `console`, `alert`, etc. + "dojo": false, //defines globals exposed by the Dojo Toolkit + "jquery": true, //defines globals exposed by the jQuery JavaScript library + "mootools": false, //defines globals exposed by the MooTools JavaScript framework + "node": true, //defines globals available when your code is running inside of the Node runtime environment + "nonstandard": false, //defines non-standard but widely adopted globals such as `escape` and `unescape` + "phantom": false, //defines globals available when your core is running inside of the PhantomJS runtime environment + "qunit": true, //defines globals available when your core is running inside of the Qqunit runtime environment + "prototypejs": false, //defines globals exposed by the Prototype JavaScript framework + "rhino": false, //defines globals available when your code is running inside of the Rhino runtime environment + "worker": true, //defines globals available when your code is running inside of a Web Worker + "wsh": false, //defines globals available when your code is running as a script for the Windows Script Host + "yui": false, //defines globals exposed by the YUI JavaScript framework + + "globals": { + "define": false, + "throttle": false, + "delay": false, + "debounce": false, + "jQuery": true, + "require": false, + "Gridster": false + }, + + // LEGACY + // These options are legacy from JSLint. Aside from bug fixes they will not + // be improved in any way and might be removed at any point. + + "nomen": false, //disallows the use of dangling `_` in variables + "onevar": false, //allows only one `var` statement per function + "passfail": false, //makes JSHint stop on the first error or warning + "white": false //make JSHint check your source code against Douglas Crockford's JavaScript coding style +} diff --git a/lib/gridster/.npmignore b/lib/gridster/.npmignore new file mode 100644 index 000000000..b348df9ef --- /dev/null +++ b/lib/gridster/.npmignore @@ -0,0 +1,4 @@ +* +!README.md +!LICENSE +!dist/**/* \ No newline at end of file diff --git a/lib/gridster/.travis.yml b/lib/gridster/.travis.yml new file mode 100644 index 000000000..3a76b5f7a --- /dev/null +++ b/lib/gridster/.travis.yml @@ -0,0 +1,10 @@ +language: node_js +node_js: + - "0.10" +install: + - npm install + - npm install -g bower grunt-cli +before_script: + - bower install +script: + - grunt build --verbose \ No newline at end of file diff --git a/lib/gridster/CHANGELOG.md b/lib/gridster/CHANGELOG.md index 6d6f1a22f..7bdaffd7e 100644 --- a/lib/gridster/CHANGELOG.md +++ b/lib/gridster/CHANGELOG.md @@ -1,3 +1,105 @@ + +### v0.6.10 (2015-05-31) + * Add Ruby on Rails support + + +### v0.6.9 (2015-05-27) + * bug fixes for positions and overlap + * dist modified to support webpack deployements + * new 'sticky' layout option which allows widgets to be places absolutely into a position on the grid. + + +### v0.6.8 (2015-04-28) + + +#### Bug Fixes + +* **gridster:** + * responsive width now resizes based off wrapper not window ([e69c3e8f](http://github.com/dsmorse/gridster.js/commit/e69c3e8f64aa4557ef032e4d0d8185e83b1aed21)) + * ensure coords instances are destroyed on widgets ([576b5ae3](http://github.com/dsmorse/gridster.js/commit/576b5ae3f0461b048d8ff9463509b860ffa8b194)) + * `resize_widget` also accepts HTMLElements ([cda560f4](http://github.com/dsmorse/gridster.js/commit/cda560f4f3ca616d03d1e3230cd2ef4e69876d9c)) + * changed "instanceof jQuery" to "instanceof $" ([c6226306](http://github.com/dsmorse/gridster.js/commit/c6226306c2ce9aa7d45d774d7de19088acba0c66)) + * wrong addition solved in add_faux_rows/cols by adding parseInt ([d9471752](http://github.com/dsmorse/gridster.js/commit/d947175257d686801154a403016fd2ec7e6d40c2), closes [#426](http://github.com/dsmorse/gridster.js/issues/426), [#425](http://github.com/dsmorse/gridster.js/issues/425)) + * preventing gridster from adding extra resize handles ([9d077da6](http://github.com/dsmorse/gridster.js/commit/9d077da676826606243c2552dc9997c492687203)) + * destroy resize_api ([b1629326](http://github.com/dsmorse/gridster.js/commit/b16293268c6aa4be2ba0c8fb1b9806e590227606), closes [#473](http://github.com/dsmorse/gridster.js/issues/473)) + * ensure widget dimensions and coords are always ints ([595a94f1](http://github.com/dsmorse/gridster.js/commit/595a94f1bdfaa4905ff51d9044e74105c81e6ff3)) + + +#### Features + +* **draggable:** autoscrolling ([d3f25f3f](http://github.com/dsmorse/gridster.js/commit/d3f25f3fbbcc738d8b3702d122533e64f37acd29)) +* **gridster:** + * add config to set custom show/hide widget methods ([7de5bbab](http://github.com/dsmorse/gridster.js/commit/7de5bbabc0a01e8188a56881782dc74d6bf111d3)) + * browserify compatibility ([43148b87](http://github.com/dsmorse/gridster.js/commit/43148b87e523352a7f9d01479c6fed3e87f46ba0)) + * Common.js support ([446852a2](http://github.com/dsmorse/gridster.js/commit/446852a260aab2e7caf772a62fbde8b518c38816), closes [#434](http://github.com/dsmorse/gridster.js/issues/434)) +* **gridster.css:** remove possible default pading ([2002c455](http://github.com/dsmorse/gridster.js/commit/2002c455957016cb441a317dbbb6e5f6662cb35a)) + + +### v0.6.7 (2015-04-16) + + +### v0.6.6 (2015-04-08) + + +### v0.6.5 (2015-04-06) + + +#### Bug Fixes + +* **gridster:** fixed bugs in centering_widgets (widgets were getting smushed when being resized ([86053f8b](http://github.com/DecksterTeam/gridster.js/commit/86053f8be3d73a9db3d7eabc595324123dbcff13)) + + +### v0.6.4 (2015-03-19) + + +#### Bug Fixes + +* **gridster:** + * added ability to center widgets in grid + + +### v0.6.3 (2015-03-06) + + +#### Bug Fixes + +* **gridster:** + * fixing resize limits when in fixed width mode feature(gridster): added fix_to_co ([6bb47dc1](http://github.com/DecksterTeam/gridster.js/commit/6bb47dc1ce36aef670b2acb7c244ec5f4ea440e0)) + + +### v0.6.2 (2015-02-23) + + +#### Bug Fixes + +* **gridster:** forcing height of gridster container to auto when in collapsed mode ([749f37a5](http://github.com/DecksterTeam/gridster.js/commit/749f37a52074bd16362528f94ab28ec314379ee3)) + + +### v0.6.1 (2015-02-21) + + +#### Bug Fixes + +* **gridster:** + * fixed expand_widget bug not expanding full width of window fix(gridster): user c ([dbc226d4](http://github.com/DecksterTeam/gridster.js/commit/dbc226d46c8224f753c07af6aff259785c60425f)) + * fixing drag limit issues when using autogrow_cols ([afd83fea](http://github.com/DecksterTeam/gridster.js/commit/afd83fead8c719615ae01ef7b5d3863701ff2243)) + * changed the way widgets were getting positioned so that margins are actually the ([a3913043](http://github.com/DecksterTeam/gridster.js/commit/a3913043579bae9f5ef28e34524ad7a8ae7dcafd)) + + +## v0.6.0 (2015-02-18) + + +#### Bug Fixes + +* **gridster:** changed the way widgets were getting positioned so that margins are actually the ([a3913043](http://github.com/DecksterTeam/gridster.js/commit/a3913043579bae9f5ef28e34524ad7a8ae7dcafd)) + +#### Features + +* **gridster:** make grid responsive ([a3913043](http://github.com/DecksterTeam/gridster.js/commit/a3913043579bae9f5ef28e34524ad7a8ae7dcafd)) + + +### v0.5.7 (2015-02-17) + ### v0.5.6 (2014-09-25) @@ -183,3 +285,60 @@ * **gridster:** drag-and-drop widget resizing ([e1924053](http://github.com/ducksboard/gridster.js/commit/e19240532de0bad35ffe6e5fc63934819390adc5)) * **utils:** add delay helper to utils ([faa6c5db](http://github.com/ducksboard/gridster.js/commit/faa6c5db0002feccf681e9f919ed583eef152773)) +dustmoo Modifications +=========== +Changelog 2013-04-3 + +Fork now handles standard behavior properly with swap allowing larger widgets to shift down. + +Changelog 2013-04-2 + +Added Demo to Repository. + +Changelog 2013-02-27 + +Added "Static widget support" Static Items default to the "static" class. + +You can customize this class by using the code below: + + $.gridster({ + static_class: 'custom_class', + draggable: { + items: ".gs_w:not(.custom_class)" + } + }); + +I have also added functions creating a much more thourough check of whether the player can occupy the space you are moving it too. +This version is much more reliable in swapping space with widgets. + +There are also new options for Maximum Rows and Maximum Columns: + + $.gridster({ + max_rows: map_rows, + max_cols: map_cols, + shift_larger_widgets_down: false + }); + +Setting the maximum amount of rows only completely works if you disable shifting larger widgets down at the moment. + + +Changelog 11-26-2012 + +Reworked swapping functionality to better handle large to small widget handling. + +--- + +Widgets of smaller or equal size to the dragged widget (player) +will swap places with the original widget. + +This causes tiles to swap left and right as well as up and down. + +By default smaller players will shift larger widgets down. + +I have added an option to prevent this behavior: + + $.gridster({ + shift_larger_widgets_down: false + }); + +By setting shift_larger_widgets_down to false, smaller widgets will not displace larger ones. diff --git a/lib/gridster/CONTRIBUTING.md b/lib/gridster/CONTRIBUTING.md index 032a9fae0..6fb9fe4c5 100644 --- a/lib/gridster/CONTRIBUTING.md +++ b/lib/gridster/CONTRIBUTING.md @@ -141,3 +141,60 @@ included in the project: **IMPORTANT**: By submitting a patch, you agree to allow the project owner to license your work under the same license as that used by the project. + + +#commit Message Guidelines +We use [automatic changelog creation](https://github.com/ajoslin/conventional-changelog), so it best if your commit messages follow the following conventions: + +### Commit Message Format +Each commit message consists of a **header**, a **body** and a **footer**. The header has a special +format that includes a **type**, a **scope** and a **subject**: + +``` +(): + + + +
+``` + +Any line of the commit message cannot be longer 100 characters! This allows the message to be easier +to read on github as well as in various git tools. + +### Type +Must be one of the following: + +* **feat**: A new feature +* **fix**: A bug fix +* **docs**: Documentation only changes +* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing + semi-colons, etc) +* **refactor**: A code change that neither fixes a bug or adds a feature +* **perf**: A code change that improves performance +* **test**: Adding missing tests +* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation + generation + +### Scope +The scope could be anything specifying place of the commit change. For example `$location`, +`$browser`, `$compile`, `$rootScope`, `ngHref`, `ngClick`, `ngView`, etc... + +### Subject +The subject contains succinct description of the change: + +* use the imperative, present tense: "change" not "changed" nor "changes" +* don't capitalize first letter +* no dot (.) at the end + +### Body +Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes" +The body should include the motivation for the change and contrast this with previous behavior. + +### Footer +The footer should contain any information about **Breaking Changes** and is also the place to +reference GitHub issues that this commit **Closes**. + +**Breaking Changes** are detected as such if the Body contains a line starting with +`BREAKING CHANGES:` The rest of the commit message is then used for this. + +A detailed explanation can be found in this [document](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/). \ No newline at end of file diff --git a/lib/gridster/Gruntfile.js b/lib/gridster/Gruntfile.js index 48be91324..8f96c2063 100644 --- a/lib/gridster/Gruntfile.js +++ b/lib/gridster/Gruntfile.js @@ -1,183 +1,233 @@ /*global module:false*/ module.exports = function(grunt) { - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - meta: { - banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("yyyy-mm-dd") %>\n' + - '<%= pkg.homepage ? "* " + pkg.homepage : "" %>\n' + - '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + - ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n\n', + 'use strict'; + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + meta: { + banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + + '<%= pkg.homepage ? "* " + pkg.homepage : "" %>\n' + + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n\n', - minibanner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("yyyy-mm-dd") %> - ' + - '<%= pkg.homepage ? "* " + pkg.homepage + " - " : "" %>' + - 'Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + - ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */ ' - }, - concat: { - options: { - stripBanners: true, - banner: '<%= meta.banner %>' - }, - dist_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js'], - dest: 'dist/jquery.<%= pkg.name %>.js' - }, + minibanner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %> - ' + + '<%= pkg.homepage ? "* " + pkg.homepage + " - " : "" %>' + + 'Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */ ' + }, + concat: { + options: { + stripBanners: true, + banner: '<%= meta.banner %>' + }, + dist_js: { + src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js'], + dest: 'dist/jquery.<%= pkg.name %>.js' + }, - dist_extras_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js', 'src/jquery.<%= pkg.name %>.extras.js'], - dest: 'dist/jquery.<%= pkg.name %>.with-extras.js' - }, + dist_extras_js: { + src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js', 'src/jquery.<%= pkg.name %>.extras.js'], + dest: 'dist/jquery.<%= pkg.name %>.with-extras.js' + } + }, + uglify: { + options: { + banner: '<%= meta.minibanner %>' + }, + dist: { + files: { + 'dist/jquery.<%= pkg.name %>.min.js': ['<%= concat.dist_js.dest %>'] + } + }, - dist_css: { - src: ['src/jquery.<%= pkg.name %>.css'], - dest: 'dist/jquery.<%= pkg.name %>.css' - }, + dist_extras: { + files: { + 'dist/jquery.<%= pkg.name %>.with-extras.min.js': ['<%= concat.dist_extras_js.dest %>'] + } + } + }, + cssmin: { + compress: { + options: { + keepSpecialComments: 0, + banner: '<%= meta.minibanner %>' + }, + files: { + 'dist/jquery.<%= pkg.name %>.min.css': ['dist/jquery.<%= pkg.name %>.css'] + } + } + }, + jshint: { + options: { + verbose: true, + reporter: require('jshint-stylish'), + jshintrc: '.jshintrc' + }, + files: ['GruntFile.js', 'src/**/*.js','sample/**/*.js', 'test/**/*.js'] + }, + yuidoc: { + compile: { + 'name': 'gridster.js', + 'description': 'gridster.js, a drag-and-drop multi-column jQuery grid plugin', + 'version': 'v<%= pkg.version %>', + 'url': 'http://gridster.net/', + 'logo': 'https://ducksboard.com/static/images/svg/logo-ducksboard-black-small.svg', + options: { + paths: 'src/', + outdir: 'gh-pages/docs/' + } + } + }, + replace: { + 'rails-version': { + src: ['lib/gridster.js-rails/version.rb'], + dest: 'lib/gridster.js-rails/version.rb', + replacements: [{ + from: /(\S*)(VERSION = ).*/g, + to: '$1$2"<%= pkg.version %>"' + }] + } + }, + copy: { + dist: { + files: [{ + expand: true, + dest: 'gh-pages/', + src: ['dist/*', 'demos/**'] + },{ + expand: true, + dest: 'dist', + src: ['src/jquery.gridster.less'] + }] + }, + rails: { + files: [{ + expand: true, + flatten: true, + dest: 'vendor/assets/javascripts/', + src: ['dist/*.js'] + }, { + expand: true, + flatten: true, + dest: 'vendor/assets/stylesheets/', + src: ['dist/*.css'] + }] + } + }, + shell: { + 'build-rails-gem': { + command: 'gem build gridster.js-rails.gemspec' + }, + 'publish-rails-gem': { + command: 'gem push gridster.js-rails-<%= pkg.version %>.gem' + } + }, + 'gh-pages': { + target: { + options: { + message: 'update docs for changes from v<%= pkg.version %> ', + base: 'gh-pages', + add: true, + push: true + }, + src: '**' + } + }, + bump: { + options: { + files: ['package.json', 'bower.json'], + updateConfigs: ['pkg'], + commit: true, + commitMessage: 'Release v%VERSION%', + commitFiles: ['package.json', 'bower.json', 'CHANGELOG.md', 'dist/'], // '-a' for all files + createTag: true, + tagName: 'v%VERSION%', + tagMessage: 'Version %VERSION%', + push: false, + pushTo: 'origin', + gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe' + } + }, + clean: { + dist: [ + 'gridster.js-rails*.gem', + '.grunt', + 'gh-pages', + 'dist', + 'vendor' + ] + }, + changelog: { + options: { + dest: 'CHANGELOG.md' + } + }, + watch: { + files: ['libs/*.js', 'src/*.js', 'src/*.less', 'Gruntfile.js'], + tasks: ['concat', 'uglify', 'less', 'cssmin'] + }, + qunit: { + files: [ + 'test/jquery.gridster.html' + ] + }, + less: { + default: { + options: { + sourceMap: true, + sourceMapFilename: 'dist/jquery.gridster.css.map' + }, + files: { + 'dist/jquery.gridster.css': 'src/jquery.gridster.less' + } + }, + demo: { + options: { + sourceMap: true, + sourceMapFilename: 'demos/assets/css/demo.css.map' + }, + files: { + 'demos/assets/css/demo.css': 'demos/assets/less/demo.less' + } + } + } + }); - dist_demo_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js'], - dest: 'gh-pages/dist/jquery.<%= pkg.name %>.js' - }, + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-yuidoc'); + grunt.loadNpmTasks('grunt-bump'); + grunt.loadNpmTasks('grunt-conventional-changelog'); + grunt.loadNpmTasks('grunt-gh-pages'); + grunt.loadNpmTasks('grunt-text-replace'); + grunt.loadNpmTasks('grunt-shell'); - dist_extras_demo_js: { - src: ['src/jquery.coords.js', 'src/jquery.collision.js', 'src/utils.js', 'src/jquery.draggable.js', 'src/jquery.<%= pkg.name %>.js', 'src/jquery.<%= pkg.name %>.extras.js'], - dest: 'gh-pages/dist/jquery.<%= pkg.name %>.with-extras.js' - }, + // Default task. + grunt.registerTask('default', ['jshint', 'concat', 'less', 'uglify', 'cssmin', 'replace:rails-version', 'copy:rails']); + grunt.registerTask('build', ['default', 'qunit', 'shell:build-rails-gem']); + grunt.registerTask('test', ['jshint','qunit']); + grunt.registerTask('docs', ['clean', 'build', 'yuidoc', 'copy:dist', 'gh-pages']); - dist_demo_css: { - src: ['src/jquery.<%= pkg.name %>.css'], - dest: 'gh-pages/dist/jquery.<%= pkg.name %>.css' - } - }, - uglify: { - options: { - banner: '<%= meta.minibanner %>' - }, - dist: { - files: { - 'dist/jquery.<%= pkg.name %>.min.js': ['<%= concat.dist_js.dest %>'] - } - }, + //builds and releases the gem files + grunt.registerTask('rails:publish', ['clean', 'build', 'shell:publish-rails-gem']); - dist_extras: { - files: { - 'dist/jquery.<%= pkg.name %>.with-extras.min.js': ['<%= concat.dist_extras_js.dest %>'] - } - }, + //use one of the four release tasks to build the artifacts for the release (it will push the docs pages only) + grunt.registerTask('release:patch', ['build', 'bump-only:patch', 'build', 'docs', 'changelog']); + grunt.registerTask('release:minor', ['build', 'bump-only:minor', 'build', 'docs', 'changelog']); + grunt.registerTask('release:major', ['build', 'bump-only:major', 'build', 'docs', 'changelog']); + grunt.registerTask('release:git', ['build', 'bump-only:git', 'build', 'docs', 'changelog']); - dist_demo: { - files: { - 'gh-pages/dist/jquery.<%= pkg.name %>.min.js': ['<%= concat.dist_js.dest %>'], - } - }, - - dist_extras_demo: { - files: { - 'gh-pages/dist/jquery.<%= pkg.name %>.with-extras.min.js': ['<%= concat.dist_extras_js.dest %>'] - } - } - }, - cssmin: { - compress: { - options: { - keepSpecialComments: 0, - banner: '<%= meta.minibanner %>' - }, - files: { - "dist/jquery.<%= pkg.name %>.min.css": ["dist/jquery.<%= pkg.name %>.css"], - "gh-pages/dist/jquery.<%= pkg.name %>.min.css": ["dist/jquery.<%= pkg.name %>.css"] - } - } - }, - jshint: { - files: ['grunt.js', 'src/**/*.js', 'test/**/*.js'] - }, - watch: { - files: ['<%= lint.files %>', 'src/jquery.<%= pkg.name %>.css'], - tasks: 'min concat' - }, - jshint: { - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - boss: true, - eqnull: true, - browser: true - }, - globals: { - jQuery: true - } - }, - yuidoc: { - compile: { - "name": 'gridster.js', - "description": 'gridster.js, a drag-and-drop multi-column jQuery grid plugin', - "version": '0.1.0', - "url": 'http://gridster.net/', - "logo": 'https://ducksboard.com/static/images/svg/logo-ducksboard-black-small.svg', - options: { - paths: "src/", - outdir: "gh-pages/docs/" - } - } - }, - - bump: { - options: { - files: ['package.json'], - updateConfigs: ['pkg'], - commit: true, - commitMessage: 'Release v%VERSION%', - commitFiles: ['package.json', 'CHANGELOG.md', 'dist/'], // '-a' for all files - createTag: true, - tagName: 'v%VERSION%', - tagMessage: 'Version %VERSION%', - push: false, - pushTo: 'origin', - gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe' - } - }, - - changelog: { - options: { - dest: 'CHANGELOG.md' - } - }, - - watch: { - files: ['libs/*.js', 'src/*.js', 'src/*.css', 'Gruntfile.js'], - tasks: ['concat', 'uglify', 'cssmin'] - } - }); - - - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); - grunt.loadNpmTasks('grunt-contrib-yuidoc'); - grunt.loadNpmTasks('grunt-bump'); - grunt.loadNpmTasks('grunt-conventional-changelog'); - - // Default task. - grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'cssmin']); - grunt.registerTask('build', ['default']); - grunt.registerTask('docs', ['yuidoc']); - - grunt.registerTask('release', ['build', 'bump-only:patch', 'build', 'docs', 'changelog']); - grunt.registerTask('release:minor', ['build', 'bump-only:minor', 'build', 'docs', 'changelog']); - grunt.registerTask('release:major', ['build', 'bump-only:major', 'build', 'docs', 'changelog']); - grunt.registerTask('release:git', ['build', 'bump-only:git', 'build', 'docs', 'changelog', 'bump-commit']); - grunt.registerTask('release:commit', ['bump-commit']); + //use this task to publish the release artifacts + grunt.registerTask('release:commit', ['bump-commit', 'shell:publish-rails-gem']); }; diff --git a/lib/gridster/README.md b/lib/gridster/README.md index 00eff37f2..22f76ab88 100644 --- a/lib/gridster/README.md +++ b/lib/gridster/README.md @@ -1,35 +1,58 @@ +[![Build Status](https://travis-ci.org/dsmorse/gridster.js.svg)](http://travis-ci.org/dsmorse/gridster.js) +[![GitHub version](https://badge.fury.io/gh/dsmorse%2Fgridster.js.svg)](http://dsmorse.github.io/gridster.js/) +[![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) +[![Gem Version](https://badge.fury.io/rb/gridster.js-rails.svg)](https://rubygems.org/gems/gridster.js-rails) +[![Bower version](https://badge.fury.io/bo/gridster-js.svg)](http://bower.io/search/?q=gridster-js) + Gridster.js =========== -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/ducksboard/gridster.js/trend.png)](https://bitdeli.com/free "Bitdeli Badge") - Gridster is a jQuery plugin that makes building intuitive draggable layouts from elements spanning multiple columns. You can even dynamically add and remove elements from the grid. More at [http://gridster.net/](http://gridster.net/). -[Releases](https://github.com/ducksboard/gridster.js/releases) +### Live Preview at: [http://dsmorse.github.io/gridster.js/](http://dsmorse.github.io/gridster.js/) -[CHANGELOG](https://github.com/ducksboard/gridster.js/blob/master/CHANGELOG.md) +[Releases](https://github.com/dsmorse/gridster.js/releases) -Gridster is maintained by Ducksboard occasionally but not actively. -@dustmoo and @pushmatrix have also write permissions as Gridster maintainers -they are. Thank you guys! +[CHANGELOG](https://github.com/dsmorse/gridster.js/blob/master/CHANGELOG.md) + +Gridster was created by Ducksboard but they have basiclly abondoned the project +and even those who had write permissions to the repo are not merging pull requests. ## Forks -Mr @dustmoo (maintainer of Gridster) has his own fork of gridster.js -with some new interesting features like widget-swapping and static widgets. +As of result of the inactivity over the last year in the [Ducksboard](https://github.com/ducksboard/gridster.js) repository, [@dsmorse](https://github.com/dsmorse/gridster.js) has created a fork +for current support. He will GLADLY accept pull requests, and will be working to merge existing +Pull Requests from Ducksboard repo. -Can be found here: [dustmoo/gridster.js](https://github.com/dustmoo/gridster.js) +## Ruby on Rails integration -@dustmoo is working in his spare time to merge all these changes into -ducksboard/gridster.js +This artifact is published to [rubygems.org](https://rubygems.org/gems/gridster.js-rails) to be consumed by ruby on rails applications. -If anyone would like to help @dustmoo improve his fork and reconcile -it with the main library he would be happy for the help. +Include gridster.js-rails in Gemfile; +``` ruby +gem 'gridster.js-rails' +``` + +and run bundle install. + +### Configuration + +Add this line to app/assets/stylesheets/application.css + +``` css + *= require jquery.gridster +``` + +Add this line to app/assets/javascripts/application.js + +``` javascript +//= require jquery.gridster +``` ## Contributing to this project diff --git a/lib/gridster/bower.json b/lib/gridster/bower.json new file mode 100644 index 000000000..aeaeb8868 --- /dev/null +++ b/lib/gridster/bower.json @@ -0,0 +1,31 @@ +{ + "name": "gridster", + "homepage": "dsmorse.github.io/gridster.js/", + "version": "0.6.10", + "dependencies": { + "jquery": "^2.1.3" + }, + "devDependencies": { + "requirejs": "^2.1.17", + "qunit": "~1.18.0" + }, + "main": [ + "dist/jquery.gridster.js", + "dist/jquery.gridster.css" + ], + "private": false, + "ignore": [ + ".bowerrc", + ".jshintrc", + ".gitignore", + ".travis.yml", + "CONTRIBUTING.md", + "Gruntfile.js", + "package.json", + "test/", + "gridster.js-rails.gemspec", + "Gemfile", + "Gemfile.lock", + "lib" + ] +} diff --git a/lib/gridster/demos/SwapDrop.html b/lib/gridster/demos/SwapDrop.html new file mode 100644 index 000000000..0f3aff562 --- /dev/null +++ b/lib/gridster/demos/SwapDrop.html @@ -0,0 +1,56 @@ + + + + + Gridster Grid Swapping Demo + + + + + + + + + + +
+

Grid with larger widgets swapping spots with smaller ones

+

This demo represents using the branch in swap mode. This works best with shift_larger_widgets_down set to "false". However, smaller widgets do + not displace larger ones.

+
+
+
    +
  • +
  • + +
  • +
  • + +
  • +
  • +
  • + +
  • +
  • + +
  • +
  • +
+
+ + diff --git a/lib/gridster/demos/adding-widgets-dynamically.html b/lib/gridster/demos/adding-widgets-dynamically.html new file mode 100644 index 000000000..0a80d3ad0 --- /dev/null +++ b/lib/gridster/demos/adding-widgets-dynamically.html @@ -0,0 +1,54 @@ + + + + Demo » Add widgets dynamically » gridster.js + + + + + + + + + +

Add widgets dynamically

+ +

Create a grid adding widgets from an Array (not from HTML) using add_widget method. Widget position (col, row) not specified.

+ +
+
    +
    + + + + + diff --git a/lib/gridster/demos/assets/img/sprite.png b/lib/gridster/demos/assets/img/sprite.png new file mode 100644 index 000000000..8f2bfc17e Binary files /dev/null and b/lib/gridster/demos/assets/img/sprite.png differ diff --git a/lib/gridster/demos/assets/js/plugins.js b/lib/gridster/demos/assets/js/plugins.js new file mode 100644 index 000000000..70bb43597 --- /dev/null +++ b/lib/gridster/demos/assets/js/plugins.js @@ -0,0 +1,25 @@ +// Avoid `console` errors in browsers that lack a console. +(function () { + var method; + var noop = function () { + }; + var methods = [ + 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', + 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', + 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', + 'timeStamp', 'trace', 'warn' + ]; + var length = methods.length; + var console = (window.console = window.console || {}); + + while (length--) { + method = methods[length]; + + // Only stub undefined methods. + if (!console[method]) { + console[method] = noop; + } + } +}()); + +// Place any jQuery/helper plugins in here. diff --git a/lib/gridster/demos/assets/less/demo.less b/lib/gridster/demos/assets/less/demo.less new file mode 100644 index 000000000..187c874e1 --- /dev/null +++ b/lib/gridster/demos/assets/less/demo.less @@ -0,0 +1,86 @@ +/*! gridster.js - v0.6.10 - 2015-05-31 +* https://dsmorse.github.io/gridster.js/ +* Copyright (c) 2015 ducksboard; Licensed MIT */ + +@bg-color: #26941f; + +body { + background: @bg-color; + font-size: 16px; + font-family: 'Helvetica Neue', Arial, sans-serif; + color: #ffffff; + margin: 0; +} +h1, h2, h3, p { + margin: 10px; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +.demo { + margin: 3em 0; + padding: 7.5em 0 5.5em; + background: @bg-color; +} +.demo:hover { + .gridster { + margin: 0 auto; + opacity: .8; + -webkit-transition: opacity .6s; + -moz-transition: opacity .6s; + -o-transition: opacity .6s; + -ms-transition: opacity .6s; + transition: opacity .6s; + } +} +.content { + color: white; +} +.gridster { + .gs-w { + background: #61A9CF; + cursor: pointer; + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + } + .player { + -webkit-box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3); + box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3); + background: #BBB; + } + .gs-w.try { + background-image: url(../img/sprite.png); + background-repeat: no-repeat; + background-position: 37px -169px; + } + .preview-holder { + border: none !important; + border-radius: 0 !important; + background: red !important; + } + ul { + background-color: #EFEFEF; + } + li { + font-size: 1em; + font-weight: bold; + text-align: center; + line-height: 100%; + } +} +ul { + list-style-type: none; +} +li { + list-style: none; + font-weight: bold; +} +.gridster-box { + position: relative; + width: 100%; + height: 100%; +} +.controls { + margin-bottom: 20px; +} diff --git a/lib/gridster/demos/chaosWidget.html b/lib/gridster/demos/chaosWidget.html new file mode 100644 index 000000000..3a5793a84 --- /dev/null +++ b/lib/gridster/demos/chaosWidget.html @@ -0,0 +1,90 @@ + + + + Demo » Resize » gridster.js + + + + + + + + + +

    Resize

    + +

    As hommage to Netflix's Chaos Monkey, this demo page is for testing. It attempts to insert a widget at a random location to ensure the overlap deconfliction locic works.

    + +
    + + +
    + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    + + + + + + + + + + +
    + + + + + + + diff --git a/lib/gridster/demos/custom-drag-handle.html b/lib/gridster/demos/custom-drag-handle.html new file mode 100644 index 000000000..2a670a4ce --- /dev/null +++ b/lib/gridster/demos/custom-drag-handle.html @@ -0,0 +1,96 @@ + + + + Demo » Custom drag handle » gridster.js + + + + + + + + + +

    Custom drag handle

    + +

    Restricts dragging from starting unless the mousedown occurs on the specified element(s).

    + + +
    +
      +
    • +
      |||
      + 0 +
    • +
    • +
      |||
      + 1 +
    • +
    • +
      |||
      + 2 +
    • +
    • +
      |||
      + 3 +
    • + +
    • +
      |||
      + 4 +
    • +
    • +
      |||
      + 5 +
    • +
    • +
      |||
      + 6 +
    • +
    • +
      |||
      + 7 +
    • +
    • +
      |||
      + 8 +
    • + +
    • +
      |||
      + 9 +
    • +
    +
    + + + + + + diff --git a/lib/gridster/demos/dynamic-grid-width.html b/lib/gridster/demos/dynamic-grid-width.html new file mode 100644 index 000000000..43ec4c06f --- /dev/null +++ b/lib/gridster/demos/dynamic-grid-width.html @@ -0,0 +1,52 @@ + + + + Demo » Dynamic grid width » gridster.js + + + + + + + + +

    Dynamic grid width

    + +

    Drag or resize some widgets to the right side. Use max_cols option to set a maximum number of columns for the grid.

    + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    +
    + + + + diff --git a/lib/gridster/demos/expandable-widgets.html b/lib/gridster/demos/expandable-widgets.html new file mode 100644 index 000000000..d618b3294 --- /dev/null +++ b/lib/gridster/demos/expandable-widgets.html @@ -0,0 +1,64 @@ + + + + Demo » Expandable widgets » gridster.js + + + + + + + + +

    Expandable widgets

    + +

    Expand widgets when hover on them using resize_widget method.

    + + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    • 10
    • +
    • 11
    • +
    +
    + + + + + + diff --git a/lib/gridster/demos/grid-from-serialize.html b/lib/gridster/demos/grid-from-serialize.html new file mode 100644 index 000000000..23897d776 --- /dev/null +++ b/lib/gridster/demos/grid-from-serialize.html @@ -0,0 +1,63 @@ + + + + Demo » Grid from serialize » gridster.js + + + + + + + + +

    Build grid from serialized positions

    + +

    Build a entire new grid from previously stored positions obtained with serialize method.

    + +
    + +
    + +
    +
      +
    +
    + + + + diff --git a/lib/gridster/demos/index.html b/lib/gridster/demos/index.html new file mode 100644 index 000000000..23feab475 --- /dev/null +++ b/lib/gridster/demos/index.html @@ -0,0 +1,34 @@ + + + + Gridster Demos + + + + + + +
    +

    Samples

    +
    + + + + + diff --git a/lib/gridster/demos/jquery.gridster.demo2.html b/lib/gridster/demos/jquery.gridster.demo2.html new file mode 100644 index 000000000..2ee586368 --- /dev/null +++ b/lib/gridster/demos/jquery.gridster.demo2.html @@ -0,0 +1,59 @@ + + + + + demo + + + + + + + + +
    +
      +
    • +
    • + +
    • +
    • +
    • +
    • +
    +
    + + + + diff --git a/lib/gridster/demos/multiple-grids.html b/lib/gridster/demos/multiple-grids.html new file mode 100644 index 000000000..bdd08a34a --- /dev/null +++ b/lib/gridster/demos/multiple-grids.html @@ -0,0 +1,71 @@ + + + + Demo » Multiple gridster intances » gridster.js + + + + + + + + +

    Multiple gridster instances with different configurations

    + +

    When using multiple gridster instances on the same page you can scope the CSS code generated by gridster using namespace config + option.

    + +

    Demo 1

    + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    +
    + + +

    Demo 2

    + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    +
    + + + + diff --git a/lib/gridster/demos/resize-limits.html b/lib/gridster/demos/resize-limits.html new file mode 100644 index 000000000..8cc8394f9 --- /dev/null +++ b/lib/gridster/demos/resize-limits.html @@ -0,0 +1,76 @@ + + + + Demo » Resize » gridster.js + + + + + + + + +

    Resize with limits

    + +

    Use resize.max_size and resize.min_size config options or data-max-sizex, data-max-sizey, + data-min-sizex and data-min-sizey HTML attributes to limit widget dimensions when resizing.

    + +

    If a global max_size is specified through the config option, can be overwrited in specified widgets with HTML data-attributes or using set_widget_max_size + method. This page has a global max limit of 4x4

    + + +
    +
      +
    • + 0 +
      + Overridden to 2, 6 +
    • +
    • + 1 +
      + Overridden max-size to 6, 2 +
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • + 9 +
      + Overridden min-size to 3, 3 +
    • +
    • 10
    • +
    • 11
    • +
    • 12
    • +
    • 13
    • +
    • 14
    • +
    • 15
    • +
    • 16
    • +
    +
    + + + + diff --git a/lib/gridster/demos/resize.html b/lib/gridster/demos/resize.html new file mode 100644 index 000000000..5f5f9f151 --- /dev/null +++ b/lib/gridster/demos/resize.html @@ -0,0 +1,65 @@ + + + + Demo » Resize » gridster.js + + + + + + + + +

    Resize

    + +

    Grab the right or bottom border and drag to the desired width or height.

    + +
    + +
    + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    +
    + + + + + + diff --git a/lib/gridster/demos/responsive.html b/lib/gridster/demos/responsive.html new file mode 100644 index 000000000..4989f5d2a --- /dev/null +++ b/lib/gridster/demos/responsive.html @@ -0,0 +1,96 @@ + + + + Gridster Responsive Demo + + + + + + + + + +
    +

    Responsive Layout

    +

    This demo represents using the branch in responsive mode. This makes the grid as wide as the screen and responds to changes in browser + width.

    +
    +
    +
      +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    • +
      +
      +
      +
    • +
    +
    + + + + diff --git a/lib/gridster/demos/serialize.html b/lib/gridster/demos/serialize.html new file mode 100644 index 000000000..3a4cfe729 --- /dev/null +++ b/lib/gridster/demos/serialize.html @@ -0,0 +1,89 @@ + + + + Demo » Serialize » gridster.js + + + + + + + + + + +

    Serialize positions

    + +

    Use serialize method to get widget positions. It returns a Array of objects that can be used as a JSON object.

    + +
    + +
    + + + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • + +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • + +
    • 9
    • + +
    • 10
    • +
    • 11
    • +
    • 12
    • +
    • 13
    • + +
    • 14
    • +
    • 15
    • + +
    • 16
    • +
    +
    + + + + diff --git a/lib/gridster/demos/sticky-postion.html b/lib/gridster/demos/sticky-postion.html new file mode 100644 index 000000000..28248dafb --- /dev/null +++ b/lib/gridster/demos/sticky-postion.html @@ -0,0 +1,50 @@ + + + + Demo » sticky position widgets » gridster.js + + + + + + + + +

    Sticky position widgets

    + +

    Widgets can be moved to an exact postion on the grid and the grid will not attempt to collapse it down to a smaller size. Also widgets will not move out of the way automatically, but will move only on mouse up

    + + +
    +
      +
        +
      • 0
      • +
      • 1
      • +
      • 2
      • +
      • 3
      • +
      • 4
      • +
      • 5
      • +
      • 6
      • +
      • 7
      • +
      • 8
      • +
      • 9
      • +
      +
    + + + + + diff --git a/lib/gridster/demos/using-drag-callbacks.html b/lib/gridster/demos/using-drag-callbacks.html new file mode 100644 index 000000000..469db738b --- /dev/null +++ b/lib/gridster/demos/using-drag-callbacks.html @@ -0,0 +1,75 @@ + + + + Demo » Using drag callbacks » gridster.js + + + + + + + + + + +

    Drag callbacks

    + +

    Drag some widgets and see the log below.

    + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    +
    + +

    Log

    + +
    + + + + diff --git a/lib/gridster/demos/using-resize-callbacks.html b/lib/gridster/demos/using-resize-callbacks.html new file mode 100644 index 000000000..5e7c86425 --- /dev/null +++ b/lib/gridster/demos/using-resize-callbacks.html @@ -0,0 +1,78 @@ + + + + Demo » Using resize callbacks » gridster.js + + + + + + + + + + +

    Resize callbacks

    + +

    Resize some widgets and see the log below.

    + + +
    +
      +
    • 0
    • +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    +
    + +

    Log

    + +
    + + + + diff --git a/lib/gridster/dist/jquery.gridster.css b/lib/gridster/dist/jquery.gridster.css index 2d1ae608f..d10e6a2e5 100644 --- a/lib/gridster/dist/jquery.gridster.css +++ b/lib/gridster/dist/jquery.gridster.css @@ -1,121 +1,109 @@ -/*! gridster.js - v0.5.6 - 2014-09-25 -* http://gridster.net/ -* Copyright (c) 2014 ducksboard; Licensed MIT */ - +/*! gridster.js - v0.6.10 - 2015-05-31 +* https://dsmorse.github.io/gridster.js/ +* Copyright (c) 2015 ducksboard; Licensed MIT */ .gridster { - position:relative; + position: relative; } - .gridster > * { - margin: 0 auto; - -webkit-transition: height .4s, width .4s; - -moz-transition: height .4s, width .4s; - -o-transition: height .4s, width .4s; - -ms-transition: height .4s, width .4s; - transition: height .4s, width .4s; + -webkit-transition: height .4s, width .4s; + -moz-transition: height .4s, width .4s; + -o-transition: height .4s, width .4s; + -ms-transition: height .4s, width .4s; + transition: height .4s, width .4s; } - .gridster .gs-w { - z-index: 2; - position: absolute; + z-index: 2; + position: absolute; +} +.gridster .preview-holder { + z-index: 1; + position: absolute; + background-color: #fff; + border-color: #fff; + opacity: 0.3; +} +.gridster .player-revert { + z-index: 10!important; + -webkit-transition: left .3s, top .3s!important; + -moz-transition: left .3s, top .3s!important; + -o-transition: left .3s, top .3s!important; + transition: left .3s, top .3s!important; +} +.gridster.collapsed { + height: auto !important; +} +.gridster.collapsed .gs-w { + position: static !important; } - .ready .gs-w:not(.preview-holder) { - -webkit-transition: opacity .3s, left .3s, top .3s; - -moz-transition: opacity .3s, left .3s, top .3s; - -o-transition: opacity .3s, left .3s, top .3s; - transition: opacity .3s, left .3s, top .3s; + -webkit-transition: opacity .3s, left .3s, top .3s; + -moz-transition: opacity .3s, left .3s, top .3s; + -o-transition: opacity .3s, left .3s, top .3s; + transition: opacity .3s, left .3s, top .3s; } - .ready .gs-w:not(.preview-holder), .ready .resize-preview-holder { - -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; - -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; - -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; - transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; } - -.gridster .preview-holder { - z-index: 1; - position: absolute; - background-color: #fff; - border-color: #fff; - opacity: 0.3; -} - -.gridster .player-revert { - z-index: 10!important; - -webkit-transition: left .3s, top .3s!important; - -moz-transition: left .3s, top .3s!important; - -o-transition: left .3s, top .3s!important; - transition: left .3s, top .3s!important; -} - .gridster .dragging, .gridster .resizing { - z-index: 10!important; - -webkit-transition: all 0s !important; - -moz-transition: all 0s !important; - -o-transition: all 0s !important; - transition: all 0s !important; + z-index: 10!important; + -webkit-transition: all 0s !important; + -moz-transition: all 0s !important; + -o-transition: all 0s !important; + transition: all 0s !important; } - - .gs-resize-handle { - position: absolute; - z-index: 1; + position: absolute; + z-index: 1; } - .gs-resize-handle-both { - width: 20px; - height: 20px; - bottom: -8px; - right: -8px; - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4='); - background-position: top left; - background-repeat: no-repeat; - cursor: se-resize; - z-index: 20; + width: 20px; + height: 20px; + bottom: -8px; + right: -8px; + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4='); + background-position: top left; + background-repeat: no-repeat; + cursor: se-resize; + z-index: 20; } - .gs-resize-handle-x { - top: 0; - bottom: 13px; - right: -5px; - width: 10px; - cursor: e-resize; + top: 0; + bottom: 13px; + right: -5px; + width: 10px; + cursor: e-resize; } - .gs-resize-handle-y { - left: 0; - right: 13px; - bottom: -5px; - height: 10px; - cursor: s-resize; + left: 0; + right: 13px; + bottom: -5px; + height: 10px; + cursor: s-resize; } - .gs-w:hover .gs-resize-handle, .resizing .gs-resize-handle { - opacity: 1; + opacity: 1; } - .gs-resize-handle, .gs-w.dragging .gs-resize-handle { - opacity: 0; + opacity: 0; } - .gs-resize-disabled .gs-resize-handle { - display: none!important; + display: none!important; } - [data-max-sizex="1"] .gs-resize-handle-x, [data-max-sizey="1"] .gs-resize-handle-y, [data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle { - display: none !important; + display: none !important; } - /* Uncomment this if you set helper : "clone" in draggable options */ /*.gridster .player { opacity:0; } -*/ \ No newline at end of file +*/ +/*# sourceMappingURL=dist/jquery.gridster.css.map */ \ No newline at end of file diff --git a/lib/gridster/dist/jquery.gridster.js b/lib/gridster/dist/jquery.gridster.js index b9ebb277b..cc3661a21 100644 --- a/lib/gridster/dist/jquery.gridster.js +++ b/lib/gridster/dist/jquery.gridster.js @@ -1,16 +1,20 @@ -/*! gridster.js - v0.5.6 - 2014-09-25 -* http://gridster.net/ -* Copyright (c) 2014 ducksboard; Licensed MIT */ +/*! gridster.js - v0.6.10 - 2015-08-05 +* https://dsmorse.github.io/gridster.js/ +* Copyright (c) 2015 ducksboard; Licensed MIT */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery')); + } + else if (typeof define === 'function' && define.amd) { define('gridster-coords', ['jquery'], factory); } else { root.GridsterCoords = factory(root.$ || root.jQuery); } }(this, function($) { + 'use strict'; /** * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height) * to simulate DOM elements on the screen. @@ -51,8 +55,8 @@ if (el && !update) { this.data = el.offset(); - this.data.width = el.width(); - this.data.height = el.height(); + this.data.width = el[0].scrollWidth; + this.data.height = el[0].scrollHeight; } if (el && update && !not_update_offsets) { @@ -63,8 +67,13 @@ var d = this.data; - typeof d.left === 'undefined' && (d.left = d.x1); - typeof d.top === 'undefined' && (d.top = d.y1); + if ( d.left === undefined ) { + d.left = d.x1; + } + + if ( d.top === undefined ) { + d.top = d.y1; + } this.coords.x1 = d.left; this.coords.y1 = d.top; @@ -111,7 +120,7 @@ return this.data('coords'); } - var ins = new Coords(this, arguments[0]); + var ins = new Coords(this); this.data('coords', ins); return ins; }; @@ -121,8 +130,11 @@ })); ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery')); + } + else if (typeof define === 'function' && define.amd) { define('gridster-collision', ['jquery', 'gridster-coords'], factory); } else { root.GridsterCollision = factory(root.$ || root.jQuery, @@ -130,7 +142,7 @@ } }(this, function($, Coords) { - + 'use strict'; var defaults = { colliders_context: document.body, overlapping_region: 'C' @@ -283,20 +295,22 @@ var area_coords = self.calculate_overlapped_area_coords( player_coords, collider_coords); var area = self.calculate_overlapped_area(area_coords); - var collider_data = { - area: area, - area_coords : area_coords, - region: region, - coords: collider_coords, - player_coords: player_coords, - el: $collider - }; + if ( 0 !== area ) { + var collider_data = { + area: area, + area_coords : area_coords, + region: region, + coords: collider_coords, + player_coords: player_coords, + el: $collider + }; - if (self.options.on_overlap) { - self.options.on_overlap.call(this, collider_data); + if (self.options.on_overlap) { + self.options.on_overlap.call(this, collider_data); + } + colliders_coords.push($collider_coords_ins); + colliders_data.push(collider_data); } - colliders_coords.push($collider_coords_ins); - colliders_data.push(collider_data); } } @@ -355,89 +369,105 @@ })); -;(function(window, undefined) { +(function (window, undefined) { + 'use strict'; + /* Delay, debounce and throttle functions taken from underscore.js + * + * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and + * Investigative Reporters & Editors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * 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. + */ - /* Delay, debounce and throttle functions taken from underscore.js - * - * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and - * Investigative Reporters & Editors - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * 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. - */ + window.delay = function (func, wait) { + var args = Array.prototype.slice.call(arguments, 2); + return setTimeout(function () { + return func.apply(null, args); + }, wait); + }; - window.delay = function(func, wait) { - var args = Array.prototype.slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); - }; + window.debounce = function (func, wait, immediate) { + var timeout; + return function () { + var context = this, args = arguments; + var later = function () { + timeout = null; + if (!immediate) { + func.apply(context, args); + } + }; + if (immediate && !timeout) { + func.apply(context, args); + } + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; - window.debounce = function(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - }; - if (immediate && !timeout) func.apply(context, args); - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }; - - window.throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = debounce( - function(){ more = throttling = false; }, wait); - return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) func.apply(context, args); - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - result = func.apply(context, args); - } - whenDone(); - throttling = true; - return result; - }; - }; + window.throttle = function (func, wait) { + var context, args, timeout, throttling, more, result; + var whenDone = debounce( + function () { + more = throttling = false; + }, wait); + return function () { + context = this; + args = arguments; + var later = function () { + timeout = null; + if (more) { + func.apply(context, args); + } + whenDone(); + }; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (throttling) { + more = true; + } else { + result = func.apply(context, args); + } + whenDone(); + throttling = true; + return result; + }; + }; })(window); ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery')); + } + else if (typeof define === 'function' && define.amd) { define('gridster-draggable', ['jquery'], factory); } else { root.GridsterDraggable = factory(root.$ || root.jQuery); } }(this, function($) { - + 'use strict'; var defaults = { items: 'li', distance: 1, @@ -466,7 +496,7 @@ var idCounter = 0; var uniqId = function() { return ++idCounter + ''; - } + }; /** * Basic drag implementation for DOM elements inside a container. @@ -498,15 +528,16 @@ * @constructor */ function Draggable(el, options) { - this.options = $.extend({}, defaults, options); - this.$document = $(document); - this.$container = $(el); - this.$dragitems = $(this.options.items, this.$container); - this.is_dragging = false; - this.player_min_left = 0 + this.options.offset_left; - this.id = uniqId(); - this.ns = '.gridster-draggable-' + this.id; - this.init(); + this.options = $.extend({}, defaults, options); + this.$document = $(document); + this.$container = $(el); + this.$scroll_container = this.options.scroll_container === window ? + $(window) : this.$container.closest(this.options.scroll_container); + this.is_dragging = false; + this.player_min_left = 0 + this.options.offset_left; + this.id = uniqId(); + this.ns = '.gridster-draggable-' + this.id; + this.init(); } Draggable.defaults = defaults; @@ -520,8 +551,8 @@ this.disabled = false; this.events(); - $(window).bind(this.nsEvent('resize'), - throttle($.proxy(this.calculate_dimensions, this), 200)); + $window.bind(this.nsEvent('resize'), + throttle($.proxy(this.calculate_dimensions, this), 200)); }; fn.nsEvent = function(ev) { @@ -532,7 +563,7 @@ this.pointer_events = { start: this.nsEvent('touchstart') + ' ' + this.nsEvent('mousedown'), move: this.nsEvent('touchmove') + ' ' + this.nsEvent('mousemove'), - end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup'), + end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup') }; this.$container.on(this.nsEvent('selectstart'), @@ -552,8 +583,7 @@ }; fn.get_actual_pos = function($el) { - var pos = $el.position(); - return pos; + return $el.position(); }; @@ -578,9 +608,13 @@ var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top); var left = Math.round(this.el_init_offset.left + - diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x); + diff_x - this.baseX + + this.$scroll_container.scrollLeft() - + this.scroll_container_offset_x); var top = Math.round(this.el_init_offset.top + - diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y); + diff_y - this.baseY + + this.$scroll_container.scrollTop() - + this.scroll_container_offset_y); if (this.options.limit) { if (left > this.player_max_left) { @@ -598,8 +632,10 @@ pointer: { left: mouse_actual_pos.left, top: mouse_actual_pos.top, - diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x), - diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y) + diff_left: diff_x + (this.$scroll_container.scrollLeft() - + this.scroll_container_offset_x), + diff_top: diff_y + (this.$scroll_container.scrollTop() - + this.scroll_container_offset_y) } }; }; @@ -630,28 +666,36 @@ var area_size = 50; var scroll_inc = 30; + var scrollDir = 'scroll' + capitalize(dir_prop); var is_x = axis === 'x'; - var window_size = is_x ? this.window_width : this.window_height; - var doc_size = is_x ? $(document).width() : $(document).height(); + var scroller_size = is_x ? this.scroller_width : this.scroller_height; + var doc_size; + if (this.$scroll_container === window){ + doc_size = is_x ? this.$scroll_container.width() : + this.$scroll_container.height(); + }else{ + doc_size = is_x ? this.$scroll_container[0].scrollWidth : + this.$scroll_container[0].scrollHeight; + } var player_size = is_x ? this.$player.width() : this.$player.height(); var next_scroll; - var scroll_offset = $window['scroll' + capitalize(dir_prop)](); - var min_window_pos = scroll_offset; - var max_window_pos = min_window_pos + window_size; + var scroll_offset = this.$scroll_container[scrollDir](); + var min_scroll_pos = scroll_offset; + var max_scroll_pos = min_scroll_pos + scroller_size; - var mouse_next_zone = max_window_pos - area_size; // down/right - var mouse_prev_zone = min_window_pos + area_size; // up/left + var mouse_next_zone = max_scroll_pos - area_size; // down/right + var mouse_prev_zone = min_scroll_pos + area_size; // up/left - var abs_mouse_pos = min_window_pos + data.pointer[dir_prop]; + var abs_mouse_pos = min_scroll_pos + data.pointer[dir_prop]; - var max_player_pos = (doc_size - window_size + player_size); + var max_player_pos = (doc_size - scroller_size + player_size); if (abs_mouse_pos >= mouse_next_zone) { next_scroll = scroll_offset + scroll_inc; if (next_scroll < max_player_pos) { - $window['scroll' + capitalize(dir_prop)](next_scroll); + this.$scroll_container[scrollDir](next_scroll); this['scroll_offset_' + axis] += scroll_inc; } } @@ -659,7 +703,7 @@ if (abs_mouse_pos <= mouse_prev_zone) { next_scroll = scroll_offset - scroll_inc; if (next_scroll > 0) { - $window['scroll' + capitalize(dir_prop)](next_scroll); + this.$scroll_container[scrollDir](next_scroll); this['scroll_offset_' + axis] -= scroll_inc; } } @@ -674,14 +718,13 @@ }; - fn.calculate_dimensions = function(e) { - this.window_height = $window.height(); - this.window_width = $window.width(); + fn.calculate_dimensions = function() { + this.scroller_height = this.$scroll_container.height(); + this.scroller_width = this.$scroll_container.width(); }; fn.drag_handler = function(e) { - var node = e.target.nodeName; // skip if drag is disabled, or click was not done with the mouse primary button if (this.disabled || e.which !== 1 && !isTouch) { return; @@ -737,7 +780,6 @@ var offset = this.$container.offset(); this.baseX = Math.round(offset.left); this.baseY = Math.round(offset.top); - this.initial_container_width = this.options.container_width || this.$container.width(); if (this.options.helper === 'clone') { this.$helper = this.$player.clone() @@ -747,13 +789,10 @@ this.helper = false; } - this.win_offset_y = $(window).scrollTop(); - this.win_offset_x = $(window).scrollLeft(); - this.scroll_offset_y = 0; - this.scroll_offset_x = 0; + this.scroll_container_offset_y = this.$scroll_container.scrollTop(); + this.scroll_container_offset_x = this.$scroll_container.scrollLeft(); this.el_init_offset = this.$player.offset(); this.player_width = this.$player.width(); - this.player_height = this.$player.height(); this.set_limits(this.options.container_width); @@ -827,7 +866,7 @@ this.$container.off(this.ns); this.$document.off(this.ns); - $(window).off(this.ns); + $window.off(this.ns); $.removeData(this.$container, 'drag'); }; @@ -841,3147 +880,4216 @@ return this.options.ignore_dragging(event); } + if (this.options.resize) { + return ! $(event.target).is(this.options.items); + } + return $(event.target).is(this.options.ignore_dragging.join(', ')); }; //jQuery adapter - $.fn.drag = function ( options ) { + $.fn.gridDraggable = function ( options ) { return new Draggable(this, options); }; + $.fn.dragg = function (options) { + return this.each(function () { + if (!$.data(this, 'drag')) { + $.data(this, 'drag', new Draggable(this, options)); + } + }); + }; + return Draggable; })); -;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { - define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); - } else { - root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, - root.GridsterCollision); - } - - }(this, function($, Draggable, Collision) { - - var defaults = { - namespace: '', - widget_selector: 'li', - widget_margins: [10, 10], - widget_base_dimensions: [400, 225], - extra_rows: 0, - extra_cols: 0, - min_cols: 1, - max_cols: Infinity, - min_rows: 15, - max_size_x: false, - autogrow_cols: false, - autogenerate_stylesheet: true, - avoid_overlapped_widgets: true, - auto_init: true, - serialize_params: function($w, wgd) { - return { - col: wgd.col, - row: wgd.row, - size_x: wgd.size_x, - size_y: wgd.size_y - }; - }, - collision: {}, - draggable: { - items: '.gs-w', - distance: 4, - ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) - }, - resize: { - enabled: false, - axes: ['both'], - handle_append_to: '', - handle_class: 'gs-resize-handle', - max_size: [Infinity, Infinity], - min_size: [1, 1] - } - }; - - /** - * @class Gridster - * @uses Draggable - * @uses Collision - * @param {HTMLElement} el The HTMLelement that contains all the widgets. - * @param {Object} [options] An Object with all options you want to - * overwrite: - * @param {HTMLElement|String} [options.widget_selector] Define who will - * be the draggable widgets. Can be a CSS Selector String or a - * collection of HTMLElements - * @param {Array} [options.widget_margins] Margin between widgets. - * The first index for the horizontal margin (left, right) and - * the second for the vertical margin (top, bottom). - * @param {Array} [options.widget_base_dimensions] Base widget dimensions - * in pixels. The first index for the width and the second for the - * height. - * @param {Number} [options.extra_cols] Add more columns in addition to - * those that have been calculated. - * @param {Number} [options.extra_rows] Add more rows in addition to - * those that have been calculated. - * @param {Number} [options.min_cols] The minimum required columns. - * @param {Number} [options.max_cols] The maximum columns possible (set to null - * for no maximum). - * @param {Number} [options.min_rows] The minimum required rows. - * @param {Number} [options.max_size_x] The maximum number of columns - * that a widget can span. - * @param {Boolean} [options.autogenerate_stylesheet] If true, all the - * CSS required to position all widgets in their respective columns - * and rows will be generated automatically and injected to the - * `` of the document. You can set this to false, and write - * your own CSS targeting rows and cols via data-attributes like so: - * `[data-col="1"] { left: 10px; }` - * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded - * from the DOM can be overlapped. It is helpful if the positions were - * bad stored in the database or if there was any conflict. - * @param {Boolean} [options.auto_init] Automatically call gridster init - * method or not when the plugin is instantiated. - * @param {Function} [options.serialize_params] Return the data you want - * for each widget in the serialization. Two arguments are passed: - * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid - * coords object (`col`, `row`, `size_x`, `size_y`). - * @param {Object} [options.collision] An Object with all options for - * Collision class you want to overwrite. See Collision docs for - * more info. - * @param {Object} [options.draggable] An Object with all options for - * Draggable class you want to overwrite. See Draggable docs for more - * info. - * @param {Object|Function} [options.draggable.ignore_dragging] Note that - * if you use a Function, and resize is enabled, you should ignore the - * resize handlers manually (options.resize.handle_class). - * @param {Object} [options.resize] An Object with resize config options. - * @param {Boolean} [options.resize.enabled] Set to true to enable - * resizing. - * @param {Array} [options.resize.axes] Axes in which widgets can be - * resized. Possible values: ['x', 'y', 'both']. - * @param {String} [options.resize.handle_append_to] Set a valid CSS - * selector to append resize handles to. - * @param {String} [options.resize.handle_class] CSS class name used - * by resize handles. - * @param {Array} [options.resize.max_size] Limit widget dimensions - * when resizing. Array values should be integers: - * `[max_cols_occupied, max_rows_occupied]` - * @param {Array} [options.resize.min_size] Limit widget dimensions - * when resizing. Array values should be integers: - * `[min_cols_occupied, min_rows_occupied]` - * @param {Function} [options.resize.start] Function executed - * when resizing starts. - * @param {Function} [otions.resize.resize] Function executed - * during the resizing. - * @param {Function} [options.resize.stop] Function executed - * when resizing stops. - * - * @constructor - */ - function Gridster(el, options) { - this.options = $.extend(true, {}, defaults, options); - this.$el = $(el); - this.$wrapper = this.$el.parent(); - this.$widgets = this.$el.children( - this.options.widget_selector).addClass('gs-w'); - this.widgets = []; - this.$changed = $([]); - this.wrapper_width = this.$wrapper.width(); - this.min_widget_width = (this.options.widget_margins[0] * 2) + - this.options.widget_base_dimensions[0]; - this.min_widget_height = (this.options.widget_margins[1] * 2) + - this.options.widget_base_dimensions[1]; - - this.generated_stylesheets = []; - this.$style_tags = $([]); - - this.options.auto_init && this.init(); - } - - Gridster.defaults = defaults; - Gridster.generated_stylesheets = []; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in ascending way. - * - * @method sort_by_row_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (!a.row) { - a = $(a).coords().grid; - b = $(b).coords().grid; - } - - if (a.row > b.row) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) placing first the empty cells upper left. - * - * @method sort_by_row_and_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_and_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row > b.row || a.row === b.row && a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects by column (representing the grid - * coords of each widget) in ascending way. - * - * @method sort_by_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method sort_by_row_desc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_desc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row + a.size_y < b.row + b.size_y) { - return 1; - } - return -1; - }); - return widgets; - }; - - - - /** Instance Methods **/ - - var fn = Gridster.prototype; - - fn.init = function() { - this.options.resize.enabled && this.setup_resize(); - this.generate_grid_and_stylesheet(); - this.get_widgets_from_DOM(); - this.set_dom_grid_height(); - this.set_dom_grid_width(); - this.$wrapper.addClass('ready'); - this.draggable(); - this.options.resize.enabled && this.resizable(); - - $(window).bind('resize.gridster', throttle( - $.proxy(this.recalculate_faux_grid, this), 200)); - }; - - - /** - * Disables dragging. - * - * @method disable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.disable = function() { - this.$wrapper.find('.player-revert').removeClass('player-revert'); - this.drag_api.disable(); - return this; - }; - - - /** - * Enables dragging. - * - * @method enable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.enable = function() { - this.drag_api.enable(); - return this; - }; - - - - /** - * Disables drag-and-drop widget resizing. - * - * @method disable - * @return {Class} Returns instance of gridster Class. - */ - fn.disable_resize = function() { - this.$el.addClass('gs-resize-disabled'); - this.resize_api.disable(); - return this; - }; - - - /** - * Enables drag-and-drop widget resizing. - * - * @method enable - * @return {Class} Returns instance of gridster Class. - */ - fn.enable_resize = function() { - this.$el.removeClass('gs-resize-disabled'); - this.resize_api.enable(); - return this; - }; - - - /** - * Add a new widget to the grid. - * - * @method add_widget - * @param {String|HTMLElement} html The string representing the HTML of the widget - * or the HTMLElement. - * @param {Number} [size_x] The nº of rows the widget occupies horizontally. - * @param {Number} [size_y] The nº of columns the widget occupies vertically. - * @param {Number} [col] The column the widget should start in. - * @param {Number} [row] The row the widget should start in. - * @param {Array} [max_size] max_size Maximun size (in units) for width and height. - * @param {Array} [min_size] min_size Minimum size (in units) for width and height. - * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. - * the widget that was just created. - */ - fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) { - var pos; - size_x || (size_x = 1); - size_y || (size_y = 1); - - if (!col & !row) { - pos = this.next_position(size_x, size_y); - } else { - pos = { - col: col, - row: row, - size_x: size_x, - size_y: size_y - }; - - this.empty_cells(col, row, size_x, size_y); - } - - var $w = $(html).attr({ - 'data-col': pos.col, - 'data-row': pos.row, - 'data-sizex' : size_x, - 'data-sizey' : size_y - }).addClass('gs-w').appendTo(this.$el).hide(); - - this.$widgets = this.$widgets.add($w); - - this.register_widget($w); - - this.add_faux_rows(pos.size_y); - //this.add_faux_cols(pos.size_x); - - if (max_size) { - this.set_widget_max_size($w, max_size); - } - - if (min_size) { - this.set_widget_min_size($w, min_size); - } - - this.set_dom_grid_width(); - this.set_dom_grid_height(); - - this.drag_api.set_limits(this.cols * this.min_widget_width); - - return $w.fadeIn(); - }; - - - /** - * Change widget size limits. - * - * @method set_widget_min_size - * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement - * representing the widget or an index representing the desired widget. - * @param {Array} min_size Minimum size (in units) for width and height. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.set_widget_min_size = function($widget, min_size) { - $widget = typeof $widget === 'number' ? - this.$widgets.eq($widget) : $widget; - - if (!$widget.length) { return this; } - - var wgd = $widget.data('coords').grid; - wgd.min_size_x = min_size[0]; - wgd.min_size_y = min_size[1]; - - return this; - }; - - - /** - * Change widget size limits. - * - * @method set_widget_max_size - * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement - * representing the widget or an index representing the desired widget. - * @param {Array} max_size Maximun size (in units) for width and height. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.set_widget_max_size = function($widget, max_size) { - $widget = typeof $widget === 'number' ? - this.$widgets.eq($widget) : $widget; - - if (!$widget.length) { return this; } - - var wgd = $widget.data('coords').grid; - wgd.max_size_x = max_size[0]; - wgd.max_size_y = max_size[1]; - - return this; - }; - - - /** - * Append the resize handle into a widget. - * - * @method add_resize_handle - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.add_resize_handle = function($w) { - var append_to = this.options.resize.handle_append_to; - $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w); - - return this; - }; - - - /** - * Change the size of a widget. Width is limited to the current grid width. - * - * @method resize_widget - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @param {Number} size_x The number of columns that will occupy the widget. - * By default size_x is limited to the space available from - * the column where the widget begins, until the last column to the right. - * @param {Number} size_y The number of rows that will occupy the widget. - * @param {Function} [callback] Function executed when the widget is removed. - * @return {HTMLElement} Returns $widget. - */ - fn.resize_widget = function($widget, size_x, size_y, callback) { - var wgd = $widget.coords().grid; - var col = wgd.col; - var max_cols = this.options.max_cols; - var old_size_y = wgd.size_y; - var old_col = wgd.col; - var new_col = old_col; - - size_x || (size_x = wgd.size_x); - size_y || (size_y = wgd.size_y); - - if (max_cols !== Infinity) { - size_x = Math.min(size_x, max_cols - col + 1); - } - - if (size_y > old_size_y) { - this.add_faux_rows(Math.max(size_y - old_size_y, 0)); - } - - var player_rcol = (col + size_x - 1); - if (player_rcol > this.cols) { - this.add_faux_cols(player_rcol - this.cols); - } - - var new_grid_data = { - col: new_col, - row: wgd.row, - size_x: size_x, - size_y: size_y - }; - - this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); - - this.set_dom_grid_height(); - this.set_dom_grid_width(); - - if (callback) { - callback.call(this, new_grid_data.size_x, new_grid_data.size_y); - } - - return $widget; - }; - - - /** - * Mutate widget dimensions and position in the grid map. - * - * @method mutate_widget_in_gridmap - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget to mutate. - * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). - * @param {Object} new_wgd New widget grid data. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) { - var old_size_x = wgd.size_x; - var old_size_y = wgd.size_y; - - var old_cells_occupied = this.get_cells_occupied(wgd); - var new_cells_occupied = this.get_cells_occupied(new_wgd); - - var empty_cols = []; - $.each(old_cells_occupied.cols, function(i, col) { - if ($.inArray(col, new_cells_occupied.cols) === -1) { - empty_cols.push(col); - } - }); - - var occupied_cols = []; - $.each(new_cells_occupied.cols, function(i, col) { - if ($.inArray(col, old_cells_occupied.cols) === -1) { - occupied_cols.push(col); - } - }); - - var empty_rows = []; - $.each(old_cells_occupied.rows, function(i, row) { - if ($.inArray(row, new_cells_occupied.rows) === -1) { - empty_rows.push(row); - } - }); - - var occupied_rows = []; - $.each(new_cells_occupied.rows, function(i, row) { - if ($.inArray(row, old_cells_occupied.rows) === -1) { - occupied_rows.push(row); - } - }); - - this.remove_from_gridmap(wgd); - - if (occupied_cols.length) { - var cols_to_empty = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget - ]; - this.empty_cells.apply(this, cols_to_empty); - } - - if (occupied_rows.length) { - var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; - this.empty_cells.apply(this, rows_to_empty); - } - - // not the same that wgd = new_wgd; - wgd.col = new_wgd.col; - wgd.row = new_wgd.row; - wgd.size_x = new_wgd.size_x; - wgd.size_y = new_wgd.size_y; - - this.add_to_gridmap(new_wgd, $widget); - - $widget.removeClass('player-revert'); - - //update coords instance attributes - $widget.data('coords').update({ - width: (new_wgd.size_x * this.options.widget_base_dimensions[0] + - ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2), - height: (new_wgd.size_y * this.options.widget_base_dimensions[1] + - ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2) - }); - - $widget.attr({ - 'data-col': new_wgd.col, - 'data-row': new_wgd.row, - 'data-sizex': new_wgd.size_x, - 'data-sizey': new_wgd.size_y - }); - - if (empty_cols.length) { - var cols_to_remove_holes = [ - empty_cols[0], new_wgd.row, - empty_cols.length, - Math.min(old_size_y, new_wgd.size_y), - $widget - ]; - - this.remove_empty_cells.apply(this, cols_to_remove_holes); - } - - if (empty_rows.length) { - var rows_to_remove_holes = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget - ]; - this.remove_empty_cells.apply(this, rows_to_remove_holes); - } - - this.move_widget_up($widget); - - return this; - }; - - - /** - * Move down widgets in cells represented by the arguments col, row, size_x, - * size_y - * - * @method empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} $exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells = function(col, row, size_x, size_y, $exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row - size_y, - size_x: size_x, - size_y: size_y - }); - - $nexts.not($exclude).each($.proxy(function(i, w) { - var wgd = $(w).coords().grid; - if ( !(wgd.row <= (row + size_y - 1))) { return; } - var diff = (row + size_y) - wgd.row; - this.move_widget_down($(w), diff); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Move up widgets below cells represented by the arguments col, row, size_x, - * size_y. - * - * @method remove_empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row, - size_x: size_x, - size_y: size_y - }); - - $nexts.not(exclude).each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), size_y ); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Get the most left column below to add a new widget. - * - * @method next_position - * @param {Number} size_x The nº of rows the widget occupies horizontally. - * @param {Number} size_y The nº of columns the widget occupies vertically. - * @return {Object} Returns a grid coords object representing the future - * widget coords. - */ - fn.next_position = function(size_x, size_y) { - size_x || (size_x = 1); - size_y || (size_y = 1); - var ga = this.gridmap; - var cols_l = ga.length; - var valid_pos = []; - var rows_l; - - for (var c = 1; c < cols_l; c++) { - rows_l = ga[c].length; - for (var r = 1; r <= rows_l; r++) { - var can_move_to = this.can_move_to({ - size_x: size_x, - size_y: size_y - }, c, r); - - if (can_move_to) { - valid_pos.push({ - col: c, - row: r, - size_y: size_y, - size_x: size_x - }); - } - } - } - - if (valid_pos.length) { - return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; - } - return false; - }; - - - /** - * Remove a widget from the grid. - * - * @method remove_widget - * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. - * @param {Boolean|Function} silent If true, widgets below the removed one - * will not move up. If a Function is passed it will be used as callback. - * @param {Function} callback Function executed when the widget is removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_widget = function(el, silent, callback) { - var $el = el instanceof $ ? el : $(el); - var wgd = $el.coords().grid; - - // if silent is a function assume it's a callback - if ($.isFunction(silent)) { - callback = silent; - silent = false; - } - - this.cells_occupied_by_placeholder = {}; - this.$widgets = this.$widgets.not($el); - - var $nexts = this.widgets_below($el); - - this.remove_from_gridmap(wgd); - - $el.fadeOut($.proxy(function() { - $el.remove(); - - if (!silent) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), wgd.size_y ); - }, this)); - } - - this.set_dom_grid_height(); - - if (callback) { - callback.call(this, el); - } - }, this)); - - return this; - }; - - - /** - * Remove all widgets from the grid. - * - * @method remove_all_widgets - * @param {Function} callback Function executed for each widget removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_all_widgets = function(callback) { - this.$widgets.each($.proxy(function(i, el){ - this.remove_widget(el, true, callback); - }, this)); - - return this; - }; - - - /** - * Returns a serialized array of the widgets in the grid. - * - * @method serialize - * @param {HTMLElement} [$widgets] The collection of jQuery wrapped - * HTMLElements you want to serialize. If no argument is passed all widgets - * will be serialized. - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize = function($widgets) { - $widgets || ($widgets = this.$widgets); - - return $widgets.map($.proxy(function(i, widget) { - var $w = $(widget); - return this.options.serialize_params($w, $w.coords().grid); - }, this)).get(); - }; - - - /** - * Returns a serialized array of the widgets that have changed their - * position. - * - * @method serialize_changed - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize_changed = function() { - return this.serialize(this.$changed); - }; - - - /** - * Convert widgets from DOM elements to "widget grid data" Objects. - * - * @method dom_to_coords - * @param {HTMLElement} $widget The widget to be converted. - */ - fn.dom_to_coords = function($widget) { - return { - 'col': parseInt($widget.attr('data-col'), 10), - 'row': parseInt($widget.attr('data-row'), 10), - 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, - 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, - 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, - 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, - 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, - 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, - 'el': $widget - }; - }; - - - /** - * Creates the grid coords object representing the widget an add it to the - * mapped array of positions. - * - * @method register_widget - * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing - * the widget, or an "widget grid data" Object with (col, row, el ...). - * @return {Boolean} Returns true if the widget final position is different - * than the original. - */ - fn.register_widget = function($el) { - var isDOM = $el instanceof jQuery; - var wgd = isDOM ? this.dom_to_coords($el) : $el; - var posChanged = false; - isDOM || ($el = wgd.el); - - var empty_upper_row = this.can_go_widget_up(wgd); - if (empty_upper_row) { - wgd.row = empty_upper_row; - $el.attr('data-row', empty_upper_row); - this.$el.trigger('gridster:positionchanged', [wgd]); - posChanged = true; - } - - if (this.options.avoid_overlapped_widgets && - !this.can_move_to( - {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) - ) { - $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); - $el.attr({ - 'data-col': wgd.col, - 'data-row': wgd.row, - 'data-sizex': wgd.size_x, - 'data-sizey': wgd.size_y - }); - posChanged = true; - } - - // attach Coord object to player data-coord attribute - $el.data('coords', $el.coords()); - // Extend Coord object with grid position info - $el.data('coords').grid = wgd; - - this.add_to_gridmap(wgd, $el); - - this.options.resize.enabled && this.add_resize_handle($el); - - return posChanged; - }; - - - /** - * Update in the mapped array of positions the value of cells represented by - * the grid coords object passed in the `grid_data` param. - * - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped - * HTMLElement, depends if you want to delete an existing position or add - * a new one. - * @method update_widget_position - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.update_widget_position = function(grid_data, value) { - this.for_each_cell_occupied(grid_data, function(col, row) { - if (!this.gridmap[col]) { return this; } - this.gridmap[col][row] = value; - }); - return this; - }; - - - /** - * Remove a widget from the mapped array of positions. - * - * @method remove_from_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_from_gridmap = function(grid_data) { - return this.update_widget_position(grid_data, false); - }; - - - /** - * Add a widget to the mapped array of positions. - * - * @method add_to_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value The value to set in the specified - * position . - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.add_to_gridmap = function(grid_data, value) { - this.update_widget_position(grid_data, value || grid_data.el); - - if (grid_data.el) { - var $widgets = this.widgets_below(grid_data.el); - $widgets.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget)); - }, this)); - } - }; - - - /** - * Make widgets draggable. - * - * @uses Draggable - * @method draggable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.draggable = function() { - var self = this; - var draggable_options = $.extend(true, {}, this.options.draggable, { - offset_left: this.options.widget_margins[0], - offset_top: this.options.widget_margins[1], - container_width: this.cols * this.min_widget_width, - limit: true, - start: function(event, ui) { - self.$widgets.filter('.player-revert') - .removeClass('player-revert'); - - self.$player = $(this); - self.$helper = $(ui.$helper); - - self.helper = !self.$helper.is(self.$player); - - self.on_start_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstart'); - }, - stop: function(event, ui) { - self.on_stop_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstop'); - }, - drag: throttle(function(event, ui) { - self.on_drag.call(self, event, ui); - self.$el.trigger('gridster:drag'); - }, 60) - }); - - this.drag_api = this.$el.drag(draggable_options); - return this; - }; - - - /** - * Bind resize events to get resize working. - * - * @method resizable - * @return {Class} Returns instance of gridster Class. - */ - fn.resizable = function() { - this.resize_api = this.$el.drag({ - items: '.' + this.options.resize.handle_class, - offset_left: this.options.widget_margins[0], - container_width: this.container_width, - move_element: false, - resize: true, - limit: this.options.autogrow_cols ? false : true, - start: $.proxy(this.on_start_resize, this), - stop: $.proxy(function(event, ui) { - delay($.proxy(function() { - this.on_stop_resize(event, ui); - }, this), 120); - }, this), - drag: throttle($.proxy(this.on_resize, this), 60) - }); - - return this; - }; - - - /** - * Setup things required for resizing. Like build templates for drag handles. - * - * @method setup_resize - * @return {Class} Returns instance of gridster Class. - */ - fn.setup_resize = function() { - this.resize_handle_class = this.options.resize.handle_class; - var axes = this.options.resize.axes; - var handle_tpl = ''; - - this.resize_handle_tpl = $.map(axes, function(type) { - return handle_tpl.replace('{type}', type); - }).join(''); - - if ($.isArray(this.options.draggable.ignore_dragging)) { - this.options.draggable.ignore_dragging.push( - '.' + this.resize_handle_class); - } - - return this; - }; - - - /** - * This function is executed when the player begins to be dragged. - * - * @method on_start_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_start_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); - - this.highest_col = this.get_highest_occupied_cell().col; - - this.$player.addClass('player'); - this.player_grid_data = this.$player.coords().grid; - this.placeholder_grid_data = $.extend({}, this.player_grid_data); - - this.set_dom_grid_height(this.$el.height() + - (this.player_grid_data.size_y * this.min_widget_height)); - - this.set_dom_grid_width(this.cols); - - var pgd_sizex = this.player_grid_data.size_x; - var cols_diff = this.cols - this.highest_col; - - if (this.options.autogrow_cols && cols_diff <= pgd_sizex) { - this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); - } - - var colliders = this.faux_grid; - var coords = this.$player.data('coords').coords; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.last_cols = []; - this.last_rows = []; - - // see jquery.collision.js - this.collision_api = this.$helper.collision( - colliders, this.options.collision); - - this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { - 'class': 'preview-holder', - 'data-row': this.$player.attr('data-row'), - 'data-col': this.$player.attr('data-col'), - css: { - width: coords.width, - height: coords.height - } - }).appendTo(this.$el); - - if (this.options.draggable.start) { - this.options.draggable.start.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player is being dragged. - * - * @method on_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_drag = function(event, ui) { - //break if dragstop has been fired - if (this.$player === null) { - return false; - } - - var abs_offset = { - left: ui.position.left + this.baseX, - top: ui.position.top + this.baseY - }; - - // auto grow cols - if (this.options.autogrow_cols) { - var prcol = this.placeholder_grid_data.col + - this.placeholder_grid_data.size_x - 1; - - // "- 1" due to adding at least 1 column in on_start_drag - if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { - this.add_faux_cols(1); - this.set_dom_grid_width(this.cols + 1); - this.drag_api.set_limits(this.container_width); - } - - this.collision_api.set_colliders(this.faux_grid); - } - - this.colliders_data = this.collision_api.get_closest_colliders( - abs_offset); - - this.on_overlapped_column_change( - this.on_start_overlapping_column, this.on_stop_overlapping_column); - - this.on_overlapped_row_change( - this.on_start_overlapping_row, this.on_stop_overlapping_row); - - - if (this.helper && this.$player) { - this.$player.css({ - 'left': ui.position.left, - 'top': ui.position.top - }); - } - - if (this.options.draggable.drag) { - this.options.draggable.drag.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player stops being dragged. - * - * @method on_stop_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_stop_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper) - .removeClass('dragging'); - - ui.position.left = ui.position.left + this.baseX; - ui.position.top = ui.position.top + this.baseY; - this.colliders_data = this.collision_api.get_closest_colliders( - ui.position); - - this.on_overlapped_column_change( - this.on_start_overlapping_column, - this.on_stop_overlapping_column - ); - - this.on_overlapped_row_change( - this.on_start_overlapping_row, - this.on_stop_overlapping_row - ); - - this.$player.addClass('player-revert').removeClass('player') - .attr({ - 'data-col': this.placeholder_grid_data.col, - 'data-row': this.placeholder_grid_data.row - }).css({ - 'left': '', - 'top': '' - }); - - this.$changed = this.$changed.add(this.$player); - - this.cells_occupied_by_player = this.get_cells_occupied( - this.placeholder_grid_data); - this.set_cells_player_occupies( - this.placeholder_grid_data.col, this.placeholder_grid_data.row); - - this.$player.coords().grid.row = this.placeholder_grid_data.row; - this.$player.coords().grid.col = this.placeholder_grid_data.col; - - if (this.options.draggable.stop) { - this.options.draggable.stop.call(this, event, ui); - } - - this.$preview_holder.remove(); - - this.$player = null; - this.$helper = null; - this.placeholder_grid_data = {}; - this.player_grid_data = {}; - this.cells_occupied_by_placeholder = {}; - this.cells_occupied_by_player = {}; - - this.set_dom_grid_height(); - this.set_dom_grid_width(); - - if (this.options.autogrow_cols) { - this.drag_api.set_limits(this.cols * this.min_widget_width); - } - }; - - - /** - * This function is executed every time a widget starts to be resized. - * - * @method on_start_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_start_resize = function(event, ui) { - this.$resized_widget = ui.$player.closest('.gs-w'); - this.resize_coords = this.$resized_widget.coords(); - this.resize_wgd = this.resize_coords.grid; - this.resize_initial_width = this.resize_coords.coords.width; - this.resize_initial_height = this.resize_coords.coords.height; - this.resize_initial_sizex = this.resize_coords.grid.size_x; - this.resize_initial_sizey = this.resize_coords.grid.size_y; - this.resize_initial_col = this.resize_coords.grid.col; - this.resize_last_sizex = this.resize_initial_sizex; - this.resize_last_sizey = this.resize_initial_sizey; - - this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || - this.options.resize.max_size[0], - this.options.max_cols - this.resize_initial_col + 1); - this.resize_max_size_y = this.resize_wgd.max_size_y || - this.options.resize.max_size[1]; - - this.resize_min_size_x = (this.resize_wgd.min_size_x || - this.options.resize.min_size[0] || 1); - this.resize_min_size_y = (this.resize_wgd.min_size_y || - this.options.resize.min_size[1] || 1); - - this.resize_initial_last_col = this.get_highest_occupied_cell().col; - - this.set_dom_grid_width(this.cols); - - this.resize_dir = { - right: ui.$player.is('.' + this.resize_handle_class + '-x'), - bottom: ui.$player.is('.' + this.resize_handle_class + '-y') - }; - - this.$resized_widget.css({ - 'min-width': this.options.widget_base_dimensions[0], - 'min-height': this.options.widget_base_dimensions[1] - }); - - var nodeName = this.$resized_widget.get(0).tagName; - this.$resize_preview_holder = $('<' + nodeName + ' />', { - 'class': 'preview-holder resize-preview-holder', - 'data-row': this.$resized_widget.attr('data-row'), - 'data-col': this.$resized_widget.attr('data-col'), - 'css': { - 'width': this.resize_initial_width, - 'height': this.resize_initial_height - } - }).appendTo(this.$el); - - this.$resized_widget.addClass('resizing'); +(function (root, factory) { + 'use strict'; + if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.draggable.js'), require('./jquery.collision.js'), require('./jquery.coords.js'), require('./utils.js')); + } + else if (typeof define === 'function' && define.amd) { + define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); + } else { + root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, + root.GridsterCollision); + } + +}(this, function ($, Draggable, Collision) { + 'use strict'; + var $window = $(window), + defaults = { + namespace: '', + widget_selector: 'li', + static_class: 'static', + widget_margins: [10, 10], + widget_base_dimensions: [400, 225], + extra_rows: 0, + extra_cols: 0, + min_cols: 1, + max_cols: Infinity, + min_rows: 1, + max_rows: 15, + autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, + auto_init: true, + center_widgets: false, + responsive_breakpoint: false, + scroll_container: window, + shift_larger_widgets_down: true, + shift_widgets_up: true, + show_element: function($el, callback) { + if (callback) { + $el.fadeIn(callback); + } else { + $el.fadeIn(); + } + }, + hide_element: function($el, callback) { + if (callback) { + $el.fadeOut(callback); + } else { + $el.fadeOut(); + } + }, + serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + }, + collision: { + wait_for_mouseup: false + }, + draggable: { + items: '.gs-w:not(.static)', + distance: 4, + ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) + }, + resize: { + enabled: false, + axes: ['both'], + handle_append_to: '', + handle_class: 'gs-resize-handle', + max_size: [Infinity, Infinity], + min_size: [1, 1] + } + }; + + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.max_cols] The maximum columns possible (set to null + * for no maximum). + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Boolean} [options.auto_init] Automatically call gridster init + * method or not when the plugin is instantiated. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Boolean} [options.shift_larger_widgets_down] Determines if how widgets get pushes + * out of the way of the player. If set to false smaller widgets will not move larger + * widgets out of their way . Defaults to true. + * @param {Boolean} [options.shift_widgets_up] Determines if the player will automatically + * condense the grid and not allow a widget to have space above it. Defaults to true. + * @param {Function} [options.show_element] Makes the given element visible. Two arguments are passed: + * `$el`: the jQuery wrapped HTMLElement, and `callback`: a function that is executed + * after the element is made visible. The callback parameter is optional. + * @param {Function} [options.hide_element] Hides the given element. Two arguments are passed: + * `$el`: the jQuery wrapped HTMLElement, and `callback`: a function that is executed + * after the element is hidden. The callback parameter is optional. + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Boolean} [options.collision.wait_for_mouseup] Default is false. + * If true then it will not move colliding widgets during drag, but only on + * mouseup. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more info. + * @param {Object|Function} [options.draggable.ignore_dragging] Note that + * if you use a Function, and resize is enabled, you should ignore the + * resize handlers manually (options.resize.handle_class). + * @param {Object} [options.resize] An Object with resize config options. + * @param {Boolean} [options.resize.enabled] Set to true to enable + * resizing. + * @param {Array} [options.resize.axes] Axes in which widgets can be + * resized. Possible values: ['x', 'y', 'both']. + * @param {String} [options.resize.handle_append_to] Set a valid CSS + * selector to append resize handles to. + * @param {String} [options.resize.handle_class] CSS class name used + * by resize handles. + * @param {Array} [options.resize.max_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[max_cols_occupied, max_rows_occupied]` + * @param {Array} [options.resize.min_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[min_cols_occupied, min_rows_occupied]` + * @param {Function} [options.resize.start] Function executed + * when resizing starts. + * @param {Function} [options.resize.resize] Function executed + * during the resizing. + * @param {Function} [options.resize.stop] Function executed + * when resizing stops. + * + * @constructor + */ + function Gridster (el, options) { + this.options = $.extend(true, {}, defaults, options); + this.options.draggable = this.options.draggable || {}; + this.options.draggable = $.extend(true, {}, this.options.draggable, + {scroll_container: this.options.scroll_container}); + this.$el = $(el); + this.$scroll_container = this.options.scroll_container === window ? + $(window) : this.$el.closest(this.options.scroll_container); + this.$wrapper = this.$el.parent(); + this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs-w'); + this.$changed = $([]); + this.w_queue = {}; + if (this.is_responsive()) { + this.min_widget_width = this.get_responsive_col_width(); + } else { + this.min_widget_width = this.options.widget_base_dimensions[0]; + } + this.min_widget_height = this.options.widget_base_dimensions[1]; + + this.min_col_count = this.options.min_cols; + this.prev_col_count = this.min_col_count; + + this.generated_stylesheets = []; + this.$style_tags = $([]); + + this.options.auto_init && this.init(); + } + + Gridster.defaults = defaults; + Gridster.generated_stylesheets = []; + + /** + * Convert properties to Integer + * + * @param {Object} obj - config object to be converted + * @return {Object} Returns the converted object. + */ + function convInt (obj) { + var props = ['col', 'row', 'size_x', 'size_y']; + var tmp = {}; + for (var i = 0, len = props.length; i < len; i++) { + var prop = props[i]; + if (!(prop in obj)) { + throw new Error('Not exists property `' + prop + '`'); + } + var val = obj[prop]; + if (!val || isNaN(val)) { + throw new Error('Invalid value of `' + prop + '` property'); + } + tmp[prop] = +val; + } + return tmp; + } + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + } + + a = convInt(a); + b = convInt(b); + if (a.row > b.row) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_and_col_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.row > b.row || a.row === b.row && a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_col_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_desc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + + /** Instance Methods **/ + + var fn = Gridster.prototype; + + fn.init = function () { + this.options.resize.enabled && this.setup_resize(); + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + this.$wrapper.addClass('ready'); + this.draggable(); + this.options.resize.enabled && this.resizable(); + + if (this.options.center_widgets) { + setTimeout($.proxy(function () { + this.center_widgets(); + }, this), 0); + } + + $window.bind('resize.gridster', throttle( + $.proxy(this.recalculate_faux_grid, this), 200)); + }; + + + /** + * Disables dragging. + * + * @method disable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.disable = function () { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + return this; + }; + + + /** + * Enables dragging. + * + * @method enable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.enable = function () { + this.drag_api.enable(); + return this; + }; + + + /** + * Disables drag-and-drop widget resizing. + * + * @method disable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.disable_resize = function () { + this.$el.addClass('gs-resize-disabled'); + this.resize_api.disable(); + return this; + }; + + + /** + * Enables drag-and-drop widget resizing. + * + * @method enable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.enable_resize = function () { + this.$el.removeClass('gs-resize-disabled'); + this.resize_api.enable(); + return this; + }; + + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @param {Array} [max_size] max_size Maximun size (in units) for width and height. + * @param {Array} [min_size] min_size Minimum size (in units) for width and height. + * @param {Function} [callback] Function executed after the widget is shown. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function (html, size_x, size_y, col, row, max_size, min_size, callback) { + var pos; + size_x || (size_x = 1); + size_y || (size_y = 1); + + if (!col && !row) { + pos = this.next_position(size_x, size_y); + } else { + pos = { + col: col, + row: row, + size_x: size_x, + size_y: size_y + }; + if (this.options.avoid_overlapped_widgets) { + this.empty_cells(col, row, size_x, size_y); + } + } + + var $w = $(html).attr({ + 'data-col': pos.col, + 'data-row': pos.row, + 'data-sizex': size_x, + 'data-sizey': size_y + }).addClass('gs-w').appendTo(this.$el).hide(); + + this.$widgets = this.$widgets.add($w); + this.$changed = this.$changed.add($w); + + this.register_widget($w); + + var reqRows = parseInt(pos.row) + (parseInt(pos.size_y) - 1); + if (this.rows < reqRows) { + this.add_faux_rows(reqRows - this.rows); + } + + if (max_size) { + this.set_widget_max_size($w, max_size); + } + + if (min_size) { + this.set_widget_min_size($w, min_size); + } + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + + if (this.options.center_widgets) { + setTimeout($.proxy(function () { + this.center_widgets(); + }, this), 0); + } + + this.options.show_element.call(this, $w, callback); + + return $w; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_min_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} min_size Minimum size (in grid units) for width and height. + * @return {Gridster} Returns instance of gridster Class. + */ + fn.set_widget_min_size = function ($widget, min_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { + return this; + } + + var wgd = $widget.data('coords').grid; + wgd.min_size_x = min_size[0]; + wgd.min_size_y = min_size[1]; + + return this; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_max_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} max_size Maximun size (in units) for width and height. + * @return {Gridster} Returns instance of gridster Class. + */ + fn.set_widget_max_size = function ($widget, max_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { + return this; + } + + var wgd = $widget.data('coords').grid; + wgd.max_size_x = max_size[0]; + wgd.max_size_y = max_size[1]; + + return this; + }; + + + /** + * Append the resize handle into a widget. + * + * @method add_resize_handle + * representing the widget. + * @return {HTMLElement} Returns instance of gridster Class. + * @param $w + */ + fn.add_resize_handle = function ($w) { + var $append_to = this.options.resize.handle_append_to ? $(this.options.resize.handle_append_to, $w) : $w; + + if (($append_to.children('span[class~=\'' + this.resize_handle_class + '\']')).length === 0) { + $(this.resize_handle_tpl).appendTo($append_to); + } + + return this; + }; + + + /** + * Change the size of a widget. Width is limited to the current grid width. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} [size_x] The number of columns that will occupy the widget. + * By default size_x is limited to the space available from + * the column where the widget begins, until the last column to the right. + * @param {Number} [size_y] The number of rows that will occupy the widget. + * @param {Function} [callback] Function executed when the widget is removed. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function ($widget, size_x, size_y, callback) { + var wgd = $widget.coords().grid; + + size_x || (size_x = wgd.size_x); + size_y || (size_y = wgd.size_y); + + //ensure the grid has the correct number of rows + if (!this.is_valid_row(wgd.row, size_y)){ + this.add_faux_rows(Math.max(this.calculate_highest_row(wgd.row, size_y) - this.rows, 0)); + } + + //ensure the grid has the correct number of cols + if (!this.is_valid_col(wgd.col, size_y)) { + this.add_faux_cols(Math.max(this.calculate_highest_row(wgd.col, size_x) - this.cols, 0)); + } + + var new_grid_data = { + col: wgd.col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Expand the widget. Width is set to the current grid width. + * + * @method expand_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of cols that will occupy the widget. + * @param {Number} size_y The number of rows that will occupy the widget. + * @param {Number} col The column to resize the widget from. + * @param {Function} [callback] Function executed when the widget is expanded. + * @return {HTMLElement} Returns $widget. + */ + fn.expand_widget = function ($widget, size_x, size_y, col, callback) { + var wgd = $widget.coords().grid; + var max_size_x = Math.floor(($(window).width() - this.options.widget_margins[0] * 2) / this.min_widget_width); + size_x = size_x || Math.min(max_size_x, this.cols); + size_y || (size_y = wgd.size_y); + + var old_size_y = wgd.size_y; + $widget.attr('pre_expand_col', wgd.col); + $widget.attr('pre_expand_sizex', wgd.size_x); + $widget.attr('pre_expand_sizey', wgd.size_y); + var new_col = col || 1; + + if (size_y > old_size_y) { + this.add_faux_rows(Math.max(size_y - old_size_y, 0)); + } + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Collapse the widget to it's pre-expanded size + * + * @method collapse_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Function} [callback] Function executed when the widget is collapsed. + * @return {HTMLElement} Returns $widget. + */ + fn.collapse_widget = function ($widget, callback) { + var wgd = $widget.coords().grid; + var size_x = parseInt($widget.attr('pre_expand_sizex')); + var size_y = parseInt($widget.attr('pre_expand_sizey')); + + var new_col = parseInt($widget.attr('pre_expand_col')); + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Fit the size of a widget to its content (best guess) + * + * @method fit_to_content + * @param $widget {HTMLElement} $widget The jQuery wrapped HTMLElement + * @param max_cols {Number} max number of columns a widget can take up + * @param max_rows {Number} max number of rows a widget can take up + * @param {Function} [callback] Function executed when the widget is fit to content. + * @return {HTMLElement} Returns $widget. + */ + fn.fit_to_content = function ($widget, max_cols, max_rows, callback) { + var wgd = $widget.coords().grid; + var width = this.$wrapper.width(); + var height = this.$wrapper.height(); + var col_size = this.options.widget_base_dimensions[0] + (2 * this.options.widget_margins[0]); + var row_size = this.options.widget_base_dimensions[1] + (2 * this.options.widget_margins[1]); + var best_cols = Math.ceil((width + (2 * this.options.widget_margins[0])) / col_size); + var best_rows = Math.ceil((height + (2 * this.options.widget_margins[1])) / row_size); + + var new_grid_data = { + col: wgd.col, + row: wgd.row, + size_x: Math.min(max_cols, best_cols), + size_y: Math.min(max_rows, best_rows) + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + + /** + * Centers widgets in grid + * + * @method center_widgets + */ + fn.center_widgets = debounce(function () { + var wrapper_width = this.$wrapper.width(); + var col_size; + if (this.is_responsive()) { + col_size = this.get_responsive_col_width(); + } else { + col_size = this.options.widget_base_dimensions[0] + (2 * this.options.widget_margins[0]); + } + var col_count = Math.floor(Math.max(Math.floor(wrapper_width / col_size), this.min_col_count) / 2) * 2; + + this.options.min_cols = col_count; + this.options.max_cols = col_count; + this.options.extra_cols = 0; + this.set_dom_grid_width(col_count); + this.cols = col_count; + + var col_dif = (col_count - this.prev_col_count) / 2; + + if (col_dif < 0) { + if (this.get_min_col() > col_dif * -1) { + this.shift_cols(col_dif); + } else { + this.resize_widget_dimensions(this.options); + } + + setTimeout($.proxy(function () { + this.resize_widget_dimensions(this.options); + }, this), 0); + + } else if (col_dif > 0) { + this.resize_widget_dimensions(this.options); + + setTimeout($.proxy(function () { + this.shift_cols(col_dif); + }, this), 0); + + } else { + this.resize_widget_dimensions(this.options); + + setTimeout($.proxy(function () { + this.resize_widget_dimensions(this.options); + }, this), 0); + + } + + this.prev_col_count = col_count; + return this; + }, 200); + + + fn.get_min_col = function () { + return Math.min.apply(Math, this.$widgets.map($.proxy(function (key, widget) { + return this.get_cells_occupied($(widget).coords().grid).cols; + }, this)).get()); + }; + + + fn.shift_cols = function (col_dif) { + var widgets_coords = this.$widgets.map($.proxy(function (i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + widgets_coords.each($.proxy(function (i, widget) { + var $widget = $(widget.el); + var wgd = $widget.coords().grid; + var col = parseInt($widget.attr('data-col')); + + var new_grid_data = { + col: Math.max(Math.round(col + col_dif), 1), + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + setTimeout($.proxy(function () { + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + }, this), 0); + }, this)); + }; + + + /** + * Change the dimensions of widgets. + * + * @method resize_widget_dimensions + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.resize_widget_dimensions = function (options) { + if (options.widget_margins) { + this.options.widget_margins = options.widget_margins; + } + + if (options.widget_base_dimensions) { + this.options.widget_base_dimensions = options.widget_base_dimensions; + } + + this.min_widget_width = (this.options.widget_margins[0] * 2) + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + this.options.widget_base_dimensions[1]; + + this.$widgets.each($.proxy(function (i, widget) { + var $widget = $(widget); + this.resize_widget($widget); + }, this)); + + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Mutate widget dimensions and position in the grid map. + * + * @method mutate_widget_in_gridmap + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget to mutate. + * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @param {Object} new_wgd New widget grid data. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.mutate_widget_in_gridmap = function ($widget, wgd, new_wgd) { + var old_size_y = wgd.size_y; + + var old_cells_occupied = this.get_cells_occupied(wgd); + var new_cells_occupied = this.get_cells_occupied(new_wgd); + + //find the cells that this widget currently occupies + var empty_cols = []; + $.each(old_cells_occupied.cols, function (i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + } + }); + + //find the cells that this widget will occupy + var occupied_cols = []; + $.each(new_cells_occupied.cols, function (i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + //find the rows that it currently occupies + var empty_rows = []; + $.each(old_cells_occupied.rows, function (i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + } + }); + + //find the rows that it will occupy + var occupied_rows = []; + $.each(new_cells_occupied.rows, function (i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + } + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget + ]; + this.empty_cells.apply(this, cols_to_empty); + } + + if (occupied_rows.length) { + var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + } + + // not the same that wgd = new_wgd; + wgd.col = new_wgd.col; + wgd.row = new_wgd.row; + wgd.size_x = new_wgd.size_x; + wgd.size_y = new_wgd.size_y; + + this.add_to_gridmap(new_wgd, $widget); + + $widget.removeClass('player-revert'); + + this.update_widget_dimensions($widget, new_wgd); + + if (empty_cols.length) { + var cols_to_remove_holes = [ + empty_cols[0], new_wgd.row, + empty_cols[empty_cols.length - 1] - empty_cols[0] + 1, + Math.min(old_size_y, new_wgd.size_y), + $widget + ]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget + ]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + this.move_widget_up($widget); + + return this; + }; + + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} [$exclude] Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function (col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row - size_y, + size_x: size_x, + size_y: size_y + }); + + $nexts.not($exclude).each($.proxy(function (i, w) { + var $w = $(w), + wgd = $w.coords().grid; + /*jshint -W018 */ + if (!(wgd.row <= (row + size_y - 1))) { + return; + } + /*jshint +W018 */ + var diff = (row + size_y) - wgd.row; + this.move_widget_down($w, diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} exclude Exclude widgets from being moved. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function (col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row, + size_x: size_x, + size_y: size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), size_y ); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function (size_x, size_y) { + size_x || (size_x = 1); + size_y || (size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + var rows_l; + + for (var c = 1; c < cols_l; c++) { + rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x: size_x, + size_y: size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col: c, + row: r, + size_y: size_y, + size_x: size_x + }); + } + } + } + + if (valid_pos.length) { + return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + fn.remove_by_grid = function (col, row) { + var $w = this.is_widget(col, row); + if ($w) { + this.remove_widget($w); + } + }; + + + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {Boolean|Function} [silent] If true, widgets below the removed one + * will not move up. If a Function is passed it will be used as callback. + * @param {Function} [callback] Function executed after the widget is removed. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function (el, silent, callback) { + var $el = el instanceof $ ? el : $(el); + if ($el.length === 0) { + //there is nothing to remove, so we can't remove it + return this; + } + var wgd = $el.coords().grid; + if (wgd === undefined) { + //there is no grid, so we can't remove it + return this; + } + + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + this.options.hide_element.call(this, $el, $.proxy(function(){ + $el.remove(); + + if (!silent) { + $nexts.each($.proxy(function (i, widget) { + this.move_widget_up($(widget), wgd.size_y); + }, this)); + } + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + + return this; + }; + + + /** + * Remove all widgets from the grid. + * + * @method remove_all_widgets + * @param {Function} callback Function executed for each widget removed. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_all_widgets = function (callback) { + this.$widgets.each($.proxy(function (i, el) { + this.remove_widget(el, true, callback); + }, this)); + + return this; + }; + + + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function ($widgets) { + $widgets || ($widgets = this.$widgets); + var result = []; + $widgets.each($.proxy(function (i, widget) { + var $w = $(widget); + if (typeof($w.coords().grid) !== 'undefined') { + result.push(this.options.serialize_params($w, $w.coords().grid)); + } + }, this)); + return result; + }; + + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function () { + return this.serialize(this.$changed); + }; + + + /** + * Convert widgets from DOM elements to "widget grid data" Objects. + * + * @method dom_to_coords + * @param {HTMLElement} $widget The widget to be converted. + */ + fn.dom_to_coords = function ($widget) { + return { + 'col': parseInt($widget.attr('data-col'), 10), + 'row': parseInt($widget.attr('data-row'), 10), + 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, + 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, + 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, + 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, + 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, + 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, + 'el': $widget + }; + }; + + /** + * Creates the grid coords object representing the widget an add it to the + * mapped array of positions. + * + * @method register_widget + * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing + * the widget, or an "widget grid data" Object with (col, row, el ...). + * @return {Boolean} Returns true if the widget final position is different + * than the original. + */ + fn.register_widget = function ($el) { + var isDOM = $el instanceof $; + var wgd = isDOM ? this.dom_to_coords($el) : $el; + var posChanged = false; + isDOM || ($el = wgd.el); + + var empty_upper_row = this.can_go_widget_up(wgd); + if (this.options.shift_widgets_up && empty_upper_row) { + wgd.row = empty_upper_row; + $el.attr('data-row', empty_upper_row); + this.$el.trigger('gridster:positionchanged', [wgd]); + posChanged = true; + } + + if (this.options.avoid_overlapped_widgets && !this.can_move_to( + {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) + ) { + $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); + $el.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + posChanged = true; + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + this.update_widget_dimensions($el, wgd); + + this.options.resize.enabled && this.add_resize_handle($el); + + return posChanged; + }; + + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function (grid_data, value) { + this.for_each_cell_occupied(grid_data, function (col, row) { + if (!this.gridmap[col]) { + return this; + } + this.gridmap[col][row] = value; + }); + return this; + }; + + + /** + * Update the width and height for a widgets coordinate data. + * + * @param {HTMLElement} $widget The widget to update. + * @param wgd {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @method update_widget_dimensions + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widget_dimensions = function ($widget, wgd) { + + var width = (wgd.size_x * (this.is_responsive() ? this.get_responsive_col_width() : this.options.widget_base_dimensions[0]) + + ((wgd.size_x - 1) * this.options.widget_margins[0])); + + var height = (wgd.size_y * this.options.widget_base_dimensions[1] + + ((wgd.size_y - 1) * this.options.widget_margins[1])); + + $widget.data('coords').update({ + width: width, + height: height + }); + + $widget.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + + return this; + }; + + + /** + * Update dimensions for all widgets in the grid. + * + * @method update_widgets_dimensions + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widgets_dimensions = function () { + $.each(this.$widgets, $.proxy(function (idx, widget) { + var wgd = $(widget).coords().grid; + if (typeof (wgd) !== 'object') { + return; + } + this.update_widget_dimensions($(widget), wgd); + }, this)); + return this; + }; + + + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function (grid_data) { + return this.update_widget_position(grid_data, false); + }; + + + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} [value] The value to set in the specified + * position . + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function (grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + }; + + + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.draggable = function () { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left: this.options.widget_margins[0], + offset_top: this.options.widget_margins[1], + container_width: (this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0]), + limit: true, + start: function (event, ui) { + self.$widgets.filter('.player-revert') + .removeClass('player-revert'); + + self.$player = $(this); + self.$helper = $(ui.$helper); + + self.helper = !self.$helper.is(self.$player); + + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop: function (event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag: throttle(function (event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); + + //this.drag_api = this.$el.gridDraggable(draggable_options); + this.drag_api = this.$el.dragg(draggable_options).data('drag'); + }; + + + /** + * Bind resize events to get resize working. + * + * @method resizable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.resizable = function () { + this.resize_api = this.$el.gridDraggable({ + items: '.' + this.options.resize.handle_class, + offset_left: this.options.widget_margins[0], + container_width: this.container_width, + move_element: false, + resize: true, + limit: this.options.max_cols !== Infinity, + scroll_container: this.options.scroll_container, + start: $.proxy(this.on_start_resize, this), + stop: $.proxy(function (event, ui) { + delay($.proxy(function () { + this.on_stop_resize(event, ui); + }, this), 120); + }, this), + drag: throttle($.proxy(this.on_resize, this), 60) + }); + + return this; + }; + + + /** + * Setup things required for resizing. Like build templates for drag handles. + * + * @method setup_resize + * @return {Gridster} Returns instance of gridster Class. + */ + fn.setup_resize = function () { + this.resize_handle_class = this.options.resize.handle_class; + var axes = this.options.resize.axes; + var handle_tpl = ''; + + this.resize_handle_tpl = $.map(axes, function (type) { + return handle_tpl.replace('{type}', type); + }).join(''); + + if ($.isArray(this.options.draggable.ignore_dragging)) { + this.options.draggable.ignore_dragging.push( + '.' + this.resize_handle_class); + } + + + return this; + }; + + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_drag = function (event, ui) { + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.highest_col = this.get_highest_occupied_cell().col; + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + this.set_dom_grid_height(this.$el.height() + + (this.player_grid_data.size_y * this.min_widget_height)); + + this.set_dom_grid_width(this.cols); + + var pgd_sizex = this.player_grid_data.size_x; + var cols_diff = this.cols - this.highest_col; + + if (this.options.max_cols === Infinity && cols_diff <= pgd_sizex) { + this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); + } + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied(this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied(this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + // see jquery.collision.js + this.collision_api = this.$helper.collision(colliders, this.options.collision); + + this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { + 'class': 'preview-holder', + 'data-row': this.$player.attr('data-row'), + 'data-col': this.$player.attr('data-col'), + css: { + width: coords.width, + height: coords.height + } + }).appendTo(this.$el); + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_drag = function (event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + } + + var margin_sides = this.options.widget_margins[0]; + + var placeholder_column = this.$preview_holder.attr('data-col'); + + var abs_offset = { + left: ui.position.left + this.baseX - (margin_sides * placeholder_column), + top: ui.position.top + this.baseY + }; + + // auto grow cols + if (this.options.max_cols === Infinity) { + var prcol = this.placeholder_grid_data.col + + this.placeholder_grid_data.size_x - 1; + + // "- 1" due to adding at least 1 column in on_start_drag + if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { + this.add_faux_cols(1); + this.set_dom_grid_width(this.cols + 1); + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + + this.collision_api.set_colliders(this.faux_grid); + } + + this.colliders_data = this.collision_api.get_closest_colliders(abs_offset); + + this.on_overlapped_column_change(this.on_start_overlapping_column, this.on_stop_overlapping_column); + + this.on_overlapped_row_change(this.on_start_overlapping_row, this.on_stop_overlapping_row); + + if (this.helper && this.$player) { + this.$player.css({ + 'left': ui.position.left, + 'top': ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_drag = function (event, ui) { + this.$helper.add(this.$player).add(this.$wrapper) + .removeClass('dragging'); + + var margin_sides = this.options.widget_margins[0]; + + var placeholder_column = this.$preview_holder.attr('data-col'); + + ui.position.left = ui.position.left + this.baseX - (margin_sides * placeholder_column); + ui.position.top = ui.position.top + this.baseY; + this.colliders_data = this.collision_api.get_closest_colliders( + ui.position); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + this.$changed = this.$changed.add(this.$player); + + // move the cells down if there is an overlap and we are in static mode + if (this.options.collision.wait_for_mouseup) { + this.for_each_cell_occupied(this.placeholder_grid_data, function (tcol, trow) { + if (this.is_widget(tcol, trow)) { + this.move_widget_down(this.is_widget(tcol, trow), this.placeholder_grid_data.size_y); + } + }); + } + + this.cells_occupied_by_player = this.get_cells_occupied(this.placeholder_grid_data); + + var col = this.placeholder_grid_data.col; + var row = this.placeholder_grid_data.row; + + this.set_cells_player_occupies(col, row); + this.$player.coords().grid.row = row; + this.$player.coords().grid.col = col; + + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); + } + + this.$player.addClass('player-revert').removeClass('player') + .attr({ + 'data-col': col, + 'data-row': row + }).css({ + 'left': '', + 'top': '' + }); + + this.$preview_holder.remove(); + + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + this.w_queue = {}; + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (this.options.max_cols === Infinity) { + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + }; + + + /** + * This function is executed every time a widget starts to be resized. + * + * @method on_start_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_resize = function (event, ui) { + this.$resized_widget = ui.$player.closest('.gs-w'); + this.resize_coords = this.$resized_widget.coords(); + this.resize_wgd = this.resize_coords.grid; + this.resize_initial_width = this.resize_coords.coords.width; + this.resize_initial_height = this.resize_coords.coords.height; + this.resize_initial_sizex = this.resize_coords.grid.size_x; + this.resize_initial_sizey = this.resize_coords.grid.size_y; + this.resize_initial_col = this.resize_coords.grid.col; + this.resize_last_sizex = this.resize_initial_sizex; + this.resize_last_sizey = this.resize_initial_sizey; + + this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || this.options.resize.max_size[0], + this.options.max_cols - this.resize_initial_col + 1); + this.resize_max_size_y = this.resize_wgd.max_size_y || this.options.resize.max_size[1]; + + this.resize_min_size_x = (this.resize_wgd.min_size_x || this.options.resize.min_size[0] || 1); + this.resize_min_size_y = (this.resize_wgd.min_size_y || this.options.resize.min_size[1] || 1); + + this.resize_initial_last_col = this.get_highest_occupied_cell().col; + + this.set_dom_grid_width(this.cols); + + this.resize_dir = { + right: ui.$player.is('.' + this.resize_handle_class + '-x'), + bottom: ui.$player.is('.' + this.resize_handle_class + '-y') + }; + + if (!this.is_responsive()) { + this.$resized_widget.css({ + 'min-width': this.options.widget_base_dimensions[0], + 'min-height': this.options.widget_base_dimensions[1] + }); + } + + var nodeName = this.$resized_widget.get(0).tagName; + this.$resize_preview_holder = $('<' + nodeName + ' />', { + 'class': 'preview-holder resize-preview-holder', + 'data-row': this.$resized_widget.attr('data-row'), + 'data-col': this.$resized_widget.attr('data-col'), + 'css': { + 'width': this.resize_initial_width, + 'height': this.resize_initial_height + } + }).appendTo(this.$el); + + this.$resized_widget.addClass('resizing'); if (this.options.resize.start) { - this.options.resize.start.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resizestart'); - }; - - - /** - * This function is executed every time a widget stops being resized. - * - * @method on_stop_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_stop_resize = function(event, ui) { - this.$resized_widget - .removeClass('resizing') - .css({ - 'width': '', - 'height': '' - }); - - delay($.proxy(function() { - this.$resize_preview_holder - .remove() - .css({ - 'min-width': '', - 'min-height': '' - }); - - if (this.options.resize.stop) { - this.options.resize.stop.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resizestop'); - }, this), 300); - - this.set_dom_grid_width(); - - if (this.options.autogrow_cols) { - this.drag_api.set_limits(this.cols * this.min_widget_width); - } - }; - - - /** - * This function is executed when a widget is being resized. - * - * @method on_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_resize = function(event, ui) { - var rel_x = (ui.pointer.diff_left); - var rel_y = (ui.pointer.diff_top); - var wbd_x = this.options.widget_base_dimensions[0]; - var wbd_y = this.options.widget_base_dimensions[1]; - var margin_x = this.options.widget_margins[0]; - var margin_y = this.options.widget_margins[1]; - var max_size_x = this.resize_max_size_x; - var min_size_x = this.resize_min_size_x; - var max_size_y = this.resize_max_size_y; - var min_size_y = this.resize_min_size_y; - var autogrow = this.options.autogrow_cols; - var width; - var max_width = Infinity; - var max_height = Infinity; - - var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); - var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); - - var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); - var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); - - var max_cols = (this.container_width / this.min_widget_width) - - this.resize_initial_col + 1; - var limit_width = ((max_cols * this.min_widget_width) - margin_x * 2); - - size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); - size_x = Math.min(max_cols, size_x); - width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); - max_width = Math.min(width, limit_width); - min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); - - size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); - max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); - min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); - - if (this.resize_dir.right) { - size_y = this.resize_initial_sizey; - } else if (this.resize_dir.bottom) { - size_x = this.resize_initial_sizex; - } - - if (autogrow) { - var last_widget_col = this.resize_initial_col + size_x - 1; - if (autogrow && this.resize_initial_last_col <= last_widget_col) { - this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); - - if (this.cols < last_widget_col) { - this.add_faux_cols(last_widget_col - this.cols); - } - } - } - - var css_props = {}; - !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( - this.resize_initial_width + rel_x, max_width), min_width)); - !this.resize_dir.right && (css_props.height = Math.max(Math.min( - this.resize_initial_height + rel_y, max_height), min_height)); - - this.$resized_widget.css(css_props); - - if (size_x !== this.resize_last_sizex || - size_y !== this.resize_last_sizey) { - - this.resize_widget(this.$resized_widget, size_x, size_y); - this.set_dom_grid_width(this.cols); - - this.$resize_preview_holder.css({ - 'width': '', - 'height': '' - }).attr({ - 'data-row': this.$resized_widget.attr('data-row'), - 'data-sizex': size_x, - 'data-sizey': size_y - }); - } - - if (this.options.resize.resize) { - this.options.resize.resize.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resize'); - - this.resize_last_sizex = size_x; - this.resize_last_sizey = size_y; - }; - - - /** - * Executes the callbacks passed as arguments when a column begins to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new column - * begins to be overlapped. The column is passed as first argument. - * @param {Function} stop_callback Function executed when a column stops - * being overlapped. The column is passed as first argument. - * @method on_overlapped_column_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_column_change = function(start_callback, stop_callback) { - if (!this.colliders_data.length) { - return this; - } - var cols = this.get_targeted_columns( - this.colliders_data[0].el.data.col); - - var last_n_cols = this.last_cols.length; - var n_cols = cols.length; - var i; - - for (i = 0; i < n_cols; i++) { - if ($.inArray(cols[i], this.last_cols) === -1) { - (start_callback || $.noop).call(this, cols[i]); - } - } - - for (i = 0; i< last_n_cols; i++) { - if ($.inArray(this.last_cols[i], cols) === -1) { - (stop_callback || $.noop).call(this, this.last_cols[i]); - } - } - - this.last_cols = cols; - - return this; - }; - - - /** - * Executes the callbacks passed as arguments when a row starts to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new row begins - * to be overlapped. The row is passed as first argument. - * @param {Function} end_callback Function executed when a row stops being - * overlapped. The row is passed as first argument. - * @method on_overlapped_row_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_row_change = function(start_callback, end_callback) { - if (!this.colliders_data.length) { - return this; - } - var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); - var last_n_rows = this.last_rows.length; - var n_rows = rows.length; - var i; - - for (i = 0; i < n_rows; i++) { - if ($.inArray(rows[i], this.last_rows) === -1) { - (start_callback || $.noop).call(this, rows[i]); - } - } - - for (i = 0; i < last_n_rows; i++) { - if ($.inArray(this.last_rows[i], rows) === -1) { - (end_callback || $.noop).call(this, this.last_rows[i]); - } - } - - this.last_rows = rows; - }; - - - /** - * Sets the current position of the player - * - * @param {Number} col - * @param {Number} row - * @param {Boolean} no_player - * @method set_player - * @return {object} - */ - fn.set_player = function(col, row, no_player) { - var self = this; - if (!no_player) { - this.empty_cells_player_occupies(); - } - var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; - var to_col = cell.col; - var to_row = row || cell.row; - - this.player_grid_data = { - col: to_col, - row: to_row, - size_y : this.player_grid_data.size_y, - size_x : this.player_grid_data.size_x - }; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - - var $overlapped_widgets = this.get_widgets_overlapped( - this.player_grid_data); - - var constraints = this.widgets_constraints($overlapped_widgets); - - this.manage_movements(constraints.can_go_up, to_col, to_row); - this.manage_movements(constraints.can_not_go_up, to_col, to_row); - - /* if there is not widgets overlapping in the new player position, - * update the new placeholder position. */ - if (!$overlapped_widgets.length) { - var pp = this.can_go_player_up(this.player_grid_data); - if (pp !== false) { - to_row = pp; - } - this.set_placeholder(to_col, to_row); - } - - return { - col: to_col, - row: to_row - }; - }; - - - /** - * See which of the widgets in the $widgets param collection can go to - * a upper row and which not. - * - * @method widgets_contraints - * @param {jQuery} $widgets A jQuery wrapped collection of - * HTMLElements. - * @return {object} Returns a literal Object with two keys: `can_go_up` & - * `can_not_go_up`. Each contains a set of HTMLElements. - */ - fn.widgets_constraints = function($widgets) { - var $widgets_can_go_up = $([]); - var $widgets_can_not_go_up; - var wgd_can_go_up = []; - var wgd_can_not_go_up = []; - - $widgets.each($.proxy(function(i, w) { - var $w = $(w); - var wgd = $w.coords().grid; - if (this.can_go_widget_up(wgd)) { - $widgets_can_go_up = $widgets_can_go_up.add($w); - wgd_can_go_up.push(wgd); - } else { - wgd_can_not_go_up.push(wgd); - } - }, this)); - - $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); - - return { - can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), - can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) - }; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method manage_movements - * @param {jQuery} $widgets A jQuery collection of HTMLElements - * representing the widgets you want to move. - * @param {Number} to_col The column to which we want to move the widgets. - * @param {Number} to_row The row to which we want to move the widgets. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.manage_movements = function($widgets, to_col, to_row) { - $.each($widgets, $.proxy(function(i, w) { - var wgd = w; - var $w = wgd.el; - - var can_go_widget_up = this.can_go_widget_up(wgd); - - if (can_go_widget_up) { - //target CAN go up - //so move widget up - this.move_widget_to($w, can_go_widget_up); - this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); - - } else { - //target can't go up - var can_go_player_up = this.can_go_player_up( - this.player_grid_data); - - if (!can_go_player_up) { - // target can't go up - // player cant't go up - // so we need to move widget down to a position that dont - // overlaps player - var y = (to_row + this.player_grid_data.size_y) - wgd.row; - - this.move_widget_down($w, y); - this.set_placeholder(to_col, to_row); - } - } - }, this)); - - return this; - }; - - /** - * Determines if there is a widget in the row and col given. Or if the - * HTMLElement passed as first argument is the player. - * - * @method is_player - * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of - * HTMLElements. - * @param {Number} [row] The column to which we want to move the widgets. - * @return {Boolean} Returns true or false. - */ - fn.is_player = function(col_or_el, row) { - if (row && !this.gridmap[col_or_el]) { return false; } - var $w = row ? this.gridmap[col_or_el][row] : col_or_el; - return $w && ($w.is(this.$player) || $w.is(this.$helper)); - }; - - - /** - * Determines if the widget that is being dragged is currently over the row - * and col given. - * - * @method is_player_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_player_in = function(col, row) { - var c = this.cells_occupied_by_player || {}; - return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; - }; - - - /** - * Determines if the placeholder is currently over the row and col given. - * - * @method is_placeholder_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in = function(col, row) { - var c = this.cells_occupied_by_placeholder || {}; - return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; - }; - - - /** - * Determines if the placeholder is currently over the column given. - * - * @method is_placeholder_in_col - * @param {Number} col The column to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in_col = function(col) { - var c = this.cells_occupied_by_placeholder || []; - return $.inArray(col, c.cols) >= 0; - }; - - - /** - * Determines if the cell represented by col and row params is empty. - * - * @method is_empty - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_empty = function(col, row) { - if (typeof this.gridmap[col] !== 'undefined') { - if(typeof this.gridmap[col][row] !== 'undefined' && - this.gridmap[col][row] === false + this.options.resize.start.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestart'); + }; + + + /** + * This function is executed every time a widget stops being resized. + * + * @method on_stop_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_resize = function (event, ui) { + this.$resized_widget + .removeClass('resizing') + .css({ + 'width': '', + 'height': '', + 'min-width': '', + 'min-height': '' + }); + + delay($.proxy(function () { + this.$resize_preview_holder + .remove() + .css({ + 'min-width': '', + 'min-height': '' + }); + + if (this.options.resize.stop) { + this.options.resize.stop.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestop'); + }, this), 300); + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + if (this.options.max_cols === Infinity) { + this.drag_api.set_limits(this.cols * this.min_widget_width); + } + }; + + + /** + * This function is executed when a widget is being resized. + * + * @method on_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_resize = function (event, ui) { + var rel_x = (ui.pointer.diff_left); + var rel_y = (ui.pointer.diff_top); + var wbd_x = this.is_responsive() ? this.get_responsive_col_width() : this.options.widget_base_dimensions[0]; + var wbd_y = this.options.widget_base_dimensions[1]; + var margin_x = this.options.widget_margins[0]; + var margin_y = this.options.widget_margins[1]; + var max_size_x = this.resize_max_size_x; + var min_size_x = this.resize_min_size_x; + var max_size_y = this.resize_max_size_y; + var min_size_y = this.resize_min_size_y; + var autogrow = this.options.max_cols === Infinity; + var width; + + var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); + var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); + + var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); + var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); + + // Max number of cols this widget can be in width + var max_cols = Math.floor((this.container_width / this.min_widget_width) - this.resize_initial_col + 1); + + var limit_width = (max_cols * this.min_widget_width) + ((max_cols - 1) * margin_x); + + size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); + size_x = Math.min(max_cols, size_x); + width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x ); + var max_width = Math.min(width, limit_width); + var min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x); + + size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); + var max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y); + var min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y); + + if (this.resize_dir.right) { + size_y = this.resize_initial_sizey; + } else if (this.resize_dir.bottom) { + size_x = this.resize_initial_sizex; + } + + if (autogrow) { + var last_widget_col = this.resize_initial_col + size_x - 1; + if (autogrow && this.resize_initial_last_col <= last_widget_col) { + this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); + + if (this.cols < last_widget_col) { + this.add_faux_cols(last_widget_col - this.cols); + } + } + } + + var css_props = {}; + !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( + this.resize_initial_width + rel_x, max_width), min_width)); + !this.resize_dir.right && (css_props.height = Math.max(Math.min( + this.resize_initial_height + rel_y, max_height), min_height)); + + this.$resized_widget.css(css_props); + + if (size_x !== this.resize_last_sizex || + size_y !== this.resize_last_sizey) { + + this.resize_widget(this.$resized_widget, size_x, size_y, false); + this.set_dom_grid_width(this.cols); + + this.$resize_preview_holder.css({ + 'width': '', + 'height': '' + }).attr({ + 'data-row': this.$resized_widget.attr('data-row'), + 'data-sizex': size_x, + 'data-sizey': size_y + }); + } + + if (this.options.resize.resize) { + this.options.resize.resize.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resize'); + + this.resize_last_sizex = size_x; + this.resize_last_sizey = size_y; + }; + + + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function (start_callback, stop_callback) { + if (!this.colliders_data.length) { + return this; + } + var cols = this.get_targeted_columns( + this.colliders_data[0].el.data.col); + + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for (i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for (i = 0; i < last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; + }; + + + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} end_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function (start_callback, end_callback) { + if (!this.colliders_data.length) { + return this; + } + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; + + for (i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } + + for (i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; + }; + + + /** + * Sets the current position of the player + * + * @param {Number} col + * @param {Number} row + * @param {Boolean} no_player + * @method set_player + * @return {object} + */ + fn.set_player = function (col, row, no_player) { + var self = this; + var swap = false; + if (!no_player) { + this.empty_cells_player_occupies(); + } + var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; + var to_col = cell.col; + var to_row = cell.row || row; + + this.player_grid_data = { + col: to_col, + row: to_row, + size_y: this.player_grid_data.size_y, + size_x: this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + + //Added placeholder for more advanced movement. + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped(this.player_grid_data); + + var player_size_y = this.player_grid_data.size_y; + var player_size_x = this.player_grid_data.size_x; + var placeholder_cells = this.cells_occupied_by_placeholder; + var $gr = this; + + + //Queue Swaps + $overlapped_widgets.each($.proxy(function (i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + var outside_col = placeholder_cells.cols[0] + player_size_x - 1; + var outside_row = placeholder_cells.rows[0] + player_size_y - 1; + if ($w.hasClass($gr.options.static_class)) { + //next iteration + return true; + } + if ($gr.options.collision.wait_for_mouseup && $gr.drag_api.is_dragging){ + //skip the swap and just 'move' the place holder + $gr.placeholder_grid_data.col = to_col; + $gr.placeholder_grid_data.row = to_row; + + $gr.cells_occupied_by_placeholder = $gr.get_cells_occupied( + $gr.placeholder_grid_data); + + $gr.$preview_holder.attr({ + 'data-row': to_row, + 'data-col': to_col + }); + } else if (wgd.size_x <= player_size_x && wgd.size_y <= player_size_y) { + if (!$gr.is_swap_occupied(placeholder_cells.cols[0], wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], wgd.row) && !$gr.is_in_queue(placeholder_cells.cols[0], wgd.row, $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], wgd.row, $w); + } + else if (!$gr.is_swap_occupied(outside_col, wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(outside_col, wgd.row) && !$gr.is_in_queue(outside_col, wgd.row, $w)) { + swap = $gr.queue_widget(outside_col, wgd.row, $w); + } + else if (!$gr.is_swap_occupied(wgd.col, placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, placeholder_cells.rows[0]) && !$gr.is_in_queue(wgd.col, placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(wgd.col, placeholder_cells.rows[0], $w); + } + else if (!$gr.is_swap_occupied(wgd.col, outside_row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, outside_row) && !$gr.is_in_queue(wgd.col, outside_row, $w)) { + swap = $gr.queue_widget(wgd.col, outside_row, $w); + } + else if (!$gr.is_swap_occupied(placeholder_cells.cols[0], placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], placeholder_cells.rows[0]) && !$gr.is_in_queue(placeholder_cells.cols[0], placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], placeholder_cells.rows[0], $w); + } else { + //in one last attempt we check for any other empty spaces + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = placeholder_cells.cols[0] + c; + var rowc = placeholder_cells.rows[0] + r; + if (!$gr.is_swap_occupied(colc, rowc, wgd.size_x, wgd.size_y) && !$gr.is_player_in(colc, rowc) && !$gr.is_in_queue(colc, rowc, $w)) { + swap = $gr.queue_widget(colc, rowc, $w); + c = player_size_x; + break; + } + } + } + + } + } else if ($gr.options.shift_larger_widgets_down && !swap) { + $overlapped_widgets.each($.proxy(function (i, w) { + var $w = $(w); + + if ($gr.can_go_down($w) && $w.coords().grid.row === $gr.player_grid_data.row) { + $gr.move_widget_down($w, $gr.player_grid_data.size_y); + $gr.set_placeholder(to_col, to_row); + } + })); + } + + $gr.clean_up_changed(); + })); + + //Move queued widgets + if (swap && this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + for (var key in this.w_queue) { + var _col = parseInt(key.split('_')[0]); + var _row = parseInt(key.split('_')[1]); + if (this.w_queue[key] !== 'full') { + this.new_move_widget_to(this.w_queue[key], _col, _row); + } + } + this.set_placeholder(to_col, to_row); + } + + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + if (this.options.shift_widgets_up) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + } + if (this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + this.set_placeholder(to_col, to_row); + } + } + + this.w_queue = {}; + + return { + col: to_col, + row: to_row + }; + }; + + + fn.is_swap_occupied = function (col, row, w_size_x, w_size_y) { + var occupied = false; + for (var c = 0; c < w_size_x; c++) { + for (var r = 0; r < w_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + '_' + rowc; + if (this.is_occupied(colc, rowc)) { + occupied = true; + } else if (key in this.w_queue) { + if (this.w_queue[key] === 'full') { + occupied = true; + continue; + } + var $tw = this.w_queue[key]; + var tgd = $tw.coords().grid; + //remove queued items if no longer under player. + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + } + } + if (rowc > parseInt(this.options.max_rows)) { + occupied = true; + } + if (colc > parseInt(this.options.max_cols)) { + occupied = true; + } + if (this.is_player_in(colc, rowc)) { + occupied = true; + } + } + } + + return occupied; + }; + + fn.can_placeholder_be_set = function (col, row, player_size_x, player_size_y) { + var can_set = true; + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var $tw = this.is_widget(colc, rowc); + //if this space is occupied and not queued for move. + if (rowc > parseInt(this.options.max_rows)) { + can_set = false; + } + if (colc > parseInt(this.options.max_cols)) { + can_set = false; + } + if (this.is_occupied(colc, rowc) && !this.is_widget_queued_and_can_move($tw)) { + can_set = false; + } + } + } + return can_set; + }; + + fn.queue_widget = function (col, row, $widget) { + var $w = $widget; + var wgd = $w.coords().grid; + var primary_key = col + '_' + row; + if (primary_key in this.w_queue) { + return false; + } + + this.w_queue[primary_key] = $w; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + '_' + rowc; + if (key === primary_key) { + continue; + } + this.w_queue[key] = 'full'; + } + } + + return true; + }; + + fn.is_widget_queued_and_can_move = function ($widget) { + var queued = false; + if ($widget === false) { + return false; + } + + for (var key in this.w_queue) { + if (this.w_queue[key] === 'full') { + continue; + } + if (this.w_queue[key].attr('data-col') === $widget.attr('data-col') && this.w_queue[key].attr('data-row') === $widget.attr('data-row')) { + queued = true; + //test whole space + var $w = this.w_queue[key]; + var dcol = parseInt(key.split('_')[0]); + var drow = parseInt(key.split('_')[1]); + var wgd = $w.coords().grid; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = dcol + c; + var rowc = drow + r; + if (this.is_player_in(colc, rowc)) { + queued = false; + } + + } + } + + } + } + + return queued; + }; + + fn.is_in_queue = function (col, row, $widget) { + var queued = false; + var key = col + '_' + row; + + if ((key in this.w_queue)) { + if (this.w_queue[key] === 'full') { + queued = true; + } else { + var $tw = this.w_queue[key]; + var tgd = $tw.coords().grid; + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + queued = false; + } else if (this.w_queue[key].attr('data-col') === $widget.attr('data-col') && this.w_queue[key].attr('data-row') === $widget.attr('data-row')) { + delete this.w_queue[key]; + queued = false; + } else { + queued = true; + } + } + } + + return queued; + }; + + + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {jQuery} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {object} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function ($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; + + $widgets.each($.proxy(function (i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + } else { + wgd_can_not_go_up.push(wgd); + } + }, this)); + + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + + return { + can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), + can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) + }; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + + * Depreciated. + * + * @method manage_movements + * @param {jQuery} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function ($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function (i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up( + this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + if (this.can_go_down($w)) { + console.log('In Move Down!'); + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + } + }, this)); + + return this; + }; + + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function (col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { + return false; + } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; + + + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function (col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function (col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function (col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; + + + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function (col, row) { + if (typeof this.gridmap[col] !== 'undefined') { + if (typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false ) { return true; } return false; } return true; - }; - - - /** - * Determines if the cell represented by col and row params is occupied. - * - * @method is_occupied - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_occupied = function(col, row) { - if (!this.gridmap[col]) { - return false; - } - - if (this.gridmap[col][row]) { - return true; - } - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row params. - * - * @method is_widget - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean|HTMLElement} Returns false if there is no widget, - * else returns the jQuery HTMLElement - */ - fn.is_widget = function(col, row) { - var cell = this.gridmap[col]; - if (!cell) { - return false; - } - - cell = cell[row]; - - if (cell) { - return cell; - } - - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row - * params and if this is under the widget that is being dragged. - * - * @method is_widget_under_player - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_widget_under_player = function(col, row) { - if (this.is_widget(col, row)) { - return this.is_player_in(col, row); - } - return false; - }; - - - /** - * Get widgets overlapping with the player or with the object passed - * representing the grid cells. - * - * @method get_widgets_under_player - * @return {HTMLElement} Returns a jQuery collection of HTMLElements - */ - fn.get_widgets_under_player = function(cells) { - cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); - var $widgets = $([]); - - $.each(cells.cols, $.proxy(function(i, col) { - $.each(cells.rows, $.proxy(function(i, row) { - if(this.is_widget(col, row)) { - $widgets = $widgets.add(this.gridmap[col][row]); - } - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * Put placeholder at the row and column specified. - * - * @method set_placeholder - * @param {Number} col The column to which we want to move the - * placeholder. - * @param {Number} row The row to which we want to move the - * placeholder. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_placeholder = function(col, row) { - var phgd = $.extend({}, this.placeholder_grid_data); - var $nexts = this.widgets_below({ - col: phgd.col, - row: phgd.row, - size_y: phgd.size_y, - size_x: phgd.size_x - }); - - // Prevents widgets go out of the grid - var right_col = (col + phgd.size_x - 1); - if (right_col > this.cols) { - col = col - (right_col - col); - } - - var moved_down = this.placeholder_grid_data.row < row; - var changed_column = this.placeholder_grid_data.col !== col; - - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.$preview_holder.attr({ - 'data-row' : row, - 'data-col' : col - }); - - if (moved_down || changed_column) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( - $(widget), this.placeholder_grid_data.col - col + phgd.size_y); - }, this)); - } - - var $widgets_under_ph = this.get_widgets_under_player( - this.cells_occupied_by_placeholder); - - if ($widgets_under_ph.length) { - $widgets_under_ph.each($.proxy(function(i, widget) { - var $w = $(widget); - this.move_widget_down( - $w, row + phgd.size_y - $w.data('coords').grid.row); - }, this)); - } - - }; - - - /** - * Determines whether the player can move to a position above. - * - * @method can_go_player_up - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @return {Number|Boolean} If the player can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_player_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - var $widgets_under_player = this.get_widgets_under_player(); - - /* generate an array with columns as index and array with upper rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - var r = p_bottom_row + 1; - upper_rows[tcol] = []; - - while (--r > 0) { - if (this.is_empty(tcol, r) || this.is_player(tcol, r) || - this.is_widget(tcol, r) && - grid_col[r].is($widgets_under_player) - ) { - upper_rows[tcol].push(r); - min_row = r < min_row ? r : min_row; - } else { - break; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(function(a, b) { - return a - b; - }); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Determines whether a widget can move to a position above. - * - * @method can_go_widget_up - * @param {Object} widget_grid_data The actual grid coords object of the - * widget we want to check. - * @return {Number|Boolean} If the widget can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_widget_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - - /* generate an array with columns as index and array with topmost rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - upper_rows[tcol] = []; - - var r = p_bottom_row + 1; - // iterate over each row - while (--r > 0) { - if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { - if (!grid_col[r].is(widget_grid_data.el)) { - break; - } - } - - if (!this.is_player(tcol, r) && - !this.is_placeholder_in(tcol, r) && - !this.is_player_in(tcol, r)) { - upper_rows[tcol].push(r); - } - - if (r < min_row) { - min_row = r; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(function(a, b) { - return a - b; - }); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Search a valid row for the widget represented by `widget_grid_data' in - * the `upper_rows` array. Iteration starts from row specified in `min_row`. - * - * @method get_valid_rows - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @param {Array} upper_rows An array with columns as index and arrays - * of valid rows as values. - * @param {Number} min_row The upper row from which the iteration will start. - * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` - * for the widget in question. - */ - fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { - var p_top_row = widget_grid_data.row; - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var size_y = widget_grid_data.size_y; - var r = min_row - 1; - var valid_rows = []; - - while (++r <= p_bottom_row ) { - var common = true; - $.each(upper_rows, function(col, rows) { - if ($.isArray(rows) && $.inArray(r, rows) === -1) { - common = false; - } - }); - - if (common === true) { - valid_rows.push(r); - if (valid_rows.length === size_y) { - break; - } - } - } - - var new_row = false; - if (size_y === 1) { - if (valid_rows[0] !== p_top_row) { - new_row = valid_rows[0] || false; - } - } else { - if (valid_rows[0] !== p_top_row) { - new_row = this.get_consecutive_numbers_index( - valid_rows, size_y); - } - } - - return new_row; - }; - - - fn.get_consecutive_numbers_index = function(arr, size_y) { - var max = arr.length; - var result = []; - var first = true; - var prev = -1; // or null? - - for (var i=0; i < max; i++) { - if (first || arr[i] === prev + 1) { - result.push(i); - if (result.length === size_y) { - break; - } - first = false; - } else { - result = []; - first = true; - } - - prev = arr[i]; - } - - return result.length >= size_y ? arr[result[0]] : false; - }; - - - /** - * Get widgets overlapping with the player. - * - * @method get_widgets_overlapped - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.get_widgets_overlapped = function() { - var $w; - var $widgets = $([]); - var used = []; - var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); - rows_from_bottom.reverse(); - - $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { - $.each(rows_from_bottom, $.proxy(function(i, row) { - // if there is a widget in the player position - if (!this.gridmap[col]) { return true; } //next iteration - var $w = this.gridmap[col][row]; - if (this.is_occupied(col, row) && !this.is_player($w) && - $.inArray($w, used) === -1 - ) { - $widgets = $widgets.add($w); - used.push($w); - } - - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * This callback is executed when the player begins to collide with a column. - * - * @method on_start_overlapping_column - * @param {Number} col The collided column. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_column = function(col) { - this.set_player(col, false); - }; - - - /** - * A callback executed when the player begins to collide with a row. - * - * @method on_start_overlapping_row - * @param {Number} row The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_row = function(row) { - this.set_player(false, row); - }; - - - /** - * A callback executed when the the player ends to collide with a column. - * - * @method on_stop_overlapping_column - * @param {Number} col The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_column = function(col) { - this.set_player(col, false); - - var self = this; - this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], - function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - }; - - - /** - * This callback is executed when the player ends to collide with a row. - * - * @method on_stop_overlapping_row - * @param {Number} row The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_row = function(row) { - this.set_player(false, row); - - var self = this; - var cols = this.cells_occupied_by_player.cols; - for (var c = 0, cl = cols.length; c < cl; c++) { - this.for_each_widget_below(cols[c], row, function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - } - }; - - - /** - * Move a widget to a specific row. The cell or cells must be empty. - * If the widget has widgets below, all of these widgets will be moved also - * if they can. - * - * @method move_widget_to - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the - * widget is going to be moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_to = function($widget, row) { - var self = this; - var widget_grid_data = $widget.coords().grid; - var diff = row - widget_grid_data.row; - var $next_widgets = this.widgets_below($widget); - - var can_move_to_new_cell = this.can_move_to( - widget_grid_data, widget_grid_data.col, row, $widget); - - if (can_move_to_new_cell === false) { - return false; - } - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', row); - this.$changed = this.$changed.add($widget); - - - $next_widgets.each(function(i, widget) { - var $w = $(widget); - var wgd = $w.coords().grid; - var can_go_up = self.can_go_widget_up(wgd); - if (can_go_up && can_go_up !== wgd.row) { - self.move_widget_to($w, can_go_up); - } - }); - - return this; - }; - - - /** - * Move up the specified widget and all below it. - * - * @method move_widget_up - * @param {HTMLElement} $widget The widget you want to move. - * @param {Number} [y_units] The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_up = function($widget, y_units) { - var el_grid_data = $widget.coords().grid; - var actual_row = el_grid_data.row; - var moved = []; - var can_go_up = true; - y_units || (y_units = 1); - - if (!this.can_go_up($widget)) { return false; } //break; - - this.for_each_column_occupied(el_grid_data, function(col) { - // can_go_up - if ($.inArray($widget, moved) === -1) { - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row - y_units; - next_row = this.can_go_up_to_row( - widget_grid_data, col, next_row); - - if (!next_row) { - return true; - } - - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = next_row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); - - moved.push($widget); - - $next_widgets.each($.proxy(function(i, widget) { - this.move_widget_up($(widget), y_units); - }, this)); - } - }); - - }; - - - /** - * Move down the specified widget and all below it. - * - * @method move_widget_down - * @param {jQuery} $widget The jQuery object representing the widget - * you want to move. - * @param {Number} y_units The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_down = function($widget, y_units) { - var el_grid_data, actual_row, moved, y_diff; - - if (y_units <= 0) { return false; } - - el_grid_data = $widget.coords().grid; - actual_row = el_grid_data.row; - moved = []; - y_diff = y_units; - - if (!$widget) { return false; } - - if ($.inArray($widget, moved) === -1) { - - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row + y_units; - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - - $next_widgets.each($.proxy(function(i, widget) { - var $w = $(widget); - var wd = $w.coords().grid; - var tmp_y = this.displacement_diff( - wd, widget_grid_data, y_diff); - - if (tmp_y > 0) { - this.move_widget_down($w, tmp_y); - } - }, this)); - - widget_grid_data.row = next_row; - this.update_widget_position(widget_grid_data, $widget); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); - - moved.push($widget); - } - }; - - - /** - * Check if the widget can move to the specified row, else returns the - * upper row possible. - * - * @method can_go_up_to_row - * @param {Number} widget_grid_data The current grid coords object of the - * widget. - * @param {Number} col The target column. - * @param {Number} row The target row. - * @return {Boolean|Number} Returns the row number if the widget can move - * to the target position, else returns false. - */ - fn.can_go_up_to_row = function(widget_grid_data, col, row) { - var ga = this.gridmap; - var result = true; - var urc = []; // upper_rows_in_columns - var actual_row = widget_grid_data.row; - var r; - - /* generate an array with columns as index and array with - * upper rows empty in the column */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = ga[tcol]; - urc[tcol] = []; - - r = actual_row; - while (r--) { - if (this.is_empty(tcol, r) && - !this.is_placeholder_in(tcol, r) - ) { - urc[tcol].push(r); - } else { - break; - } - } - - if (!urc[tcol].length) { - result = false; - return true; - } - - }); - - if (!result) { return false; } - - /* get common rows starting from upper position in all the columns - * that widget occupies */ - r = row; - for (r = 1; r < actual_row; r++) { - var common = true; - - for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { - if (urc[uc] && $.inArray(r, urc[uc]) === -1) { - common = false; - } - } - - if (common === true) { - result = r; - break; - } - } - - return result; - }; - - - fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { - var actual_row = widget_grid_data.row; - var diffs = []; - var parent_max_y = parent_bgd.row + parent_bgd.size_y; - - this.for_each_column_occupied(widget_grid_data, function(col) { - var temp_y_units = 0; - - for (var r = parent_max_y; r < actual_row; r++) { - if (this.is_empty(col, r)) { - temp_y_units = temp_y_units + 1; - } - } - - diffs.push(temp_y_units); - }); - - var max_diff = Math.max.apply(Math, diffs); - y_units = (y_units - max_diff); - - return y_units > 0 ? y_units : 0; - }; - - - /** - * Get widgets below a widget. - * - * @method widgets_below - * @param {HTMLElement} $el The jQuery wrapped HTMLElement. - * @return {jQuery} A jQuery collection of HTMLElements. - */ - fn.widgets_below = function($el) { - var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; - var self = this; - var ga = this.gridmap; - var next_row = el_grid_data.row + el_grid_data.size_y - 1; - var $nexts = $([]); - - this.for_each_column_occupied(el_grid_data, function(col) { - self.for_each_widget_below(col, next_row, function(tcol, trow) { - if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { - $nexts = $nexts.add(this); - return true; // break - } - }); - }); - - return Gridster.sort_by_row_asc($nexts); - }; - - - /** - * Update the array of mapped positions with the new player position. - * - * @method set_cells_player_occupies - * @param {Number} col The new player col. - * @param {Number} col The new player row. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_cells_player_occupies = function(col, row) { - this.remove_from_gridmap(this.placeholder_grid_data); - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - this.add_to_gridmap(this.placeholder_grid_data, this.$player); - return this; - }; - - - /** - * Remove from the array of mapped positions the reference to the player. - * - * @method empty_cells_player_occupies - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells_player_occupies = function() { - this.remove_from_gridmap(this.placeholder_grid_data); - return this; - }; - - - fn.can_go_up = function($el) { - var el_grid_data = $el.coords().grid; - var initial_row = el_grid_data.row; - var prev_row = initial_row - 1; - var ga = this.gridmap; - var upper_rows_by_column = []; - - var result = true; - if (initial_row === 1) { return false; } - - this.for_each_column_occupied(el_grid_data, function(col) { - var $w = this.is_widget(col, prev_row); - - if (this.is_occupied(col, prev_row) || - this.is_player(col, prev_row) || - this.is_placeholder_in(col, prev_row) || - this.is_player_in(col, prev_row) - ) { - result = false; - return true; //break - } - }); - - return result; - }; - - - /** - * Check if it's possible to move a widget to a specific col/row. It takes - * into account the dimensions (`size_y` and `size_x` attrs. of the grid - * coords object) the widget occupies. - * - * @method can_move_to - * @param {Object} widget_grid_data The grid coords object that represents - * the widget. - * @param {Object} col The col to check. - * @param {Object} row The row to check. - * @param {Number} [max_row] The max row allowed. - * @return {Boolean} Returns true if all cells are empty, else return false. - */ - fn.can_move_to = function(widget_grid_data, col, row, max_row) { - var ga = this.gridmap; - var $w = widget_grid_data.el; - var future_wd = { - size_y: widget_grid_data.size_y, - size_x: widget_grid_data.size_x, - col: col, - row: row - }; - var result = true; - - //Prevents widgets go out of the grid - var right_col = col + widget_grid_data.size_x - 1; - if (right_col > this.cols) { - return false; - } - - if (max_row && max_row < row + widget_grid_data.size_y - 1) { - return false; - } - - this.for_each_cell_occupied(future_wd, function(tcol, trow) { - var $tw = this.is_widget(tcol, trow); - if ($tw && (!widget_grid_data.el || $tw.is($w))) { - result = false; - } - }); - - return result; - }; - - - /** - * Given the leftmost column returns all columns that are overlapping - * with the player. - * - * @method get_targeted_columns - * @param {Number} [from_col] The leftmost column. - * @return {Array} Returns an array with column numbers. - */ - fn.get_targeted_columns = function(from_col) { - var max = (from_col || this.player_grid_data.col) + - (this.player_grid_data.size_x - 1); - var cols = []; - for (var col = from_col; col <= max; col++) { - cols.push(col); - } - return cols; - }; - - - /** - * Given the upper row returns all rows that are overlapping with the player. - * - * @method get_targeted_rows - * @param {Number} [from_row] The upper row. - * @return {Array} Returns an array with row numbers. - */ - fn.get_targeted_rows = function(from_row) { - var max = (from_row || this.player_grid_data.row) + - (this.player_grid_data.size_y - 1); - var rows = []; - for (var row = from_row; row <= max; row++) { - rows.push(row); - } - return rows; - }; - - /** - * Get all columns and rows that a widget occupies. - * - * @method get_cells_occupied - * @param {Object} el_grid_data The grid coords object of the widget. - * @return {Object} Returns an object like `{ cols: [], rows: []}`. - */ - fn.get_cells_occupied = function(el_grid_data) { - var cells = { cols: [], rows: []}; - var i; - if (arguments[1] instanceof $) { - el_grid_data = arguments[1].coords().grid; - } - - for (i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - cells.cols.push(col); - } - - for (i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - cells.rows.push(row); - } - - return cells; - }; - - - /** - * Iterate over the cells occupied by a widget executing a function for - * each one. - * - * @method for_each_cell_occupied - * @param {Object} el_grid_data The grid coords object that represents the - * widget. - * @param {Function} callback The function to execute on each column - * iteration. Column and row are passed as arguments. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_cell_occupied = function(grid_data, callback) { - this.for_each_column_occupied(grid_data, function(col) { - this.for_each_row_occupied(grid_data, function(row) { - callback.call(this, col, row); - }); - }); - return this; - }; - - - /** - * Iterate over the columns occupied by a widget executing a function for - * each one. - * - * @method for_each_column_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The column number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_column_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - callback.call(this, col, el_grid_data); - } - }; - - - /** - * Iterate over the rows occupied by a widget executing a function for - * each one. - * - * @method for_each_row_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The row number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_row_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - callback.call(this, row, el_grid_data); - } - }; - - - - fn._traversing_widgets = function(type, direction, col, row, callback) { - var ga = this.gridmap; - if (!ga[col]) { return; } - - var cr, max; - var action = type + '/' + direction; - if (arguments[2] instanceof $) { - var el_grid_data = arguments[2].coords().grid; - col = el_grid_data.col; - row = el_grid_data.row; - callback = arguments[3]; - } - var matched = []; - var trow = row; - - - var methods = { - 'for_each/above': function() { - while (trow--) { - if (trow > 0 && this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } - } - } - }, - 'for_each/below': function() { - for (trow = row + 1, max = ga[col].length; trow < max; trow++) { - if (this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } - } - } - } - }; - - if (methods[action]) { - methods[action].call(this); - } - }; - - - /** - * Iterate over each widget above the column and row specified. - * - * @method for_each_widget_above - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery - * wrapped HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_above = function(col, row, callback) { - this._traversing_widgets('for_each', 'above', col, row, callback); - return this; - }; - - - /** - * Iterate over each widget below the column and row specified. - * - * @method for_each_widget_below - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery wrapped - * HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_below = function(col, row, callback) { - this._traversing_widgets('for_each', 'below', col, row, callback); - return this; - }; - - - /** - * Returns the highest occupied cell in the grid. - * - * @method get_highest_occupied_cell - * @return {Object} Returns an object with `col` and `row` numbers. - */ - fn.get_highest_occupied_cell = function() { - var r; - var gm = this.gridmap; - var rl = gm[1].length; - var rows = [], cols = []; - var row_in_col = []; - for (var c = gm.length - 1; c >= 1; c--) { - for (r = rl - 1; r >= 1; r--) { - if (this.is_widget(c, r)) { - rows.push(r); - cols.push(c); - break; - } - } - } - - return { - col: Math.max.apply(Math, cols), - row: Math.max.apply(Math, rows) - }; - }; - - - fn.get_widgets_from = function(col, row) { - var ga = this.gridmap; - var $widgets = $(); - - if (col) { - $widgets = $widgets.add( - this.$widgets.filter(function() { - var tcol = $(this).attr('data-col'); - return (tcol === col || tcol > col); - }) - ); - } - - if (row) { - $widgets = $widgets.add( - this.$widgets.filter(function() { - var trow = $(this).attr('data-row'); - return (trow === row || trow > row); - }) - ); - } - - return $widgets; - }; - - - /** - * Set the current height of the parent grid. - * - * @method set_dom_grid_height - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_height = function(height) { - if (typeof height === 'undefined') { - var r = this.get_highest_occupied_cell().row; - height = r * this.min_widget_height; - } - - this.container_height = height; - this.$el.css('height', this.container_height); - return this; - }; - - /** - * Set the current width of the parent grid. - * - * @method set_dom_grid_width - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_width = function(cols) { - if (typeof cols === 'undefined') { - cols = this.get_highest_occupied_cell().col; - } - - var max_cols = (this.options.autogrow_cols ? this.options.max_cols : - this.cols); - - cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); - this.container_width = cols * this.min_widget_width; - this.$el.css('width', this.container_width); - return this; - }; - - - /** - * It generates the neccessary styles to position the widgets. - * - * @method generate_stylesheet - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_stylesheet = function(opts) { - var styles = ''; - var max_size_x = this.options.max_size_x || this.cols; - var max_rows = 0; - var max_cols = 0; - var i; - var rules; - - opts || (opts = {}); - opts.cols || (opts.cols = this.cols); - opts.rows || (opts.rows = this.rows); - opts.namespace || (opts.namespace = this.options.namespace); - opts.widget_base_dimensions || - (opts.widget_base_dimensions = this.options.widget_base_dimensions); - opts.widget_margins || - (opts.widget_margins = this.options.widget_margins); - opts.min_widget_width = (opts.widget_margins[0] * 2) + - opts.widget_base_dimensions[0]; - opts.min_widget_height = (opts.widget_margins[1] * 2) + - opts.widget_base_dimensions[1]; - - // don't duplicate stylesheets for the same configuration - var serialized_opts = $.param(opts); - if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { - return false; - } - - this.generated_stylesheets.push(serialized_opts); - Gridster.generated_stylesheets.push(serialized_opts); - - /* generate CSS styles for cols */ - for (i = opts.cols; i >= 0; i--) { - styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + - ((i * opts.widget_base_dimensions[0]) + - (i * opts.widget_margins[0]) + - ((i + 1) * opts.widget_margins[0])) + 'px; }\n'); - } - - /* generate CSS styles for rows */ - for (i = opts.rows; i >= 0; i--) { - styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + - ((i * opts.widget_base_dimensions[1]) + - (i * opts.widget_margins[1]) + - ((i + 1) * opts.widget_margins[1]) ) + 'px; }\n'); - } - - for (var y = 1; y <= opts.rows; y++) { - styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + - (y * opts.widget_base_dimensions[1] + - (y - 1) * (opts.widget_margins[1] * 2)) + 'px; }\n'); - } - - for (var x = 1; x <= max_size_x; x++) { - styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + - (x * opts.widget_base_dimensions[0] + - (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n'); - } - - this.remove_style_tags(); - - return this.add_style_tag(styles); - }; - - - /** - * Injects the given CSS as string to the head of the document. - * - * @method add_style_tag - * @param {String} css The styles to apply. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_style_tag = function(css) { - var d = document; - var tag = d.createElement('style'); - - d.getElementsByTagName('head')[0].appendChild(tag); - tag.setAttribute('type', 'text/css'); - - if (tag.styleSheet) { - tag.styleSheet.cssText = css; - } else { - tag.appendChild(document.createTextNode(css)); - } - - this.$style_tags = this.$style_tags.add(tag); - - return this; - }; - - - /** - * Remove the style tag with the associated id from the head of the document - * - * @method remove_style_tag - * @return {Object} Returns the instance of the Gridster class. - */ - fn.remove_style_tags = function() { - var all_styles = Gridster.generated_stylesheets; - var ins_styles = this.generated_stylesheets; - - this.$style_tags.remove(); - - Gridster.generated_stylesheets = $.map(all_styles, function(s) { - if ($.inArray(s, ins_styles) === -1) { return s; } - }); - }; - - - /** - * Generates a faux grid to collide with it when a widget is dragged and - * detect row or column that we want to go. - * - * @method generate_faux_grid - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_faux_grid = function(rows, cols) { - this.faux_grid = []; - this.gridmap = []; - var col; - var row; - for (col = cols; col > 0; col--) { - this.gridmap[col] = []; - for (row = rows; row > 0; row--) { - this.add_faux_cell(row, col); - } - } - return this; - }; - - - /** - * Add cell to the faux grid. - * - * @method add_faux_cell - * @param {Number} row The row for the new faux cell. - * @param {Number} col The col for the new faux cell. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cell = function(row, col) { - var coords = $({ - left: this.baseX + ((col - 1) * this.min_widget_width), - top: this.baseY + (row -1) * this.min_widget_height, - width: this.min_widget_width, - height: this.min_widget_height, - col: col, - row: row, - original_col: col, - original_row: row - }).coords(); - - if (!$.isArray(this.gridmap[col])) { - this.gridmap[col] = []; - } - - this.gridmap[col][row] = false; - this.faux_grid.push(coords); - - return this; - }; - - - /** - * Add rows to the faux grid. - * - * @method add_faux_rows - * @param {Number} rows The number of rows you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_rows = function(rows) { - var actual_rows = this.rows; - var max_rows = actual_rows + (rows || 1); - - for (var r = max_rows; r > actual_rows; r--) { - for (var c = this.cols; c >= 1; c--) { - this.add_faux_cell(r, c); - } - } - - this.rows = max_rows; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - /** - * Add cols to the faux grid. - * - * @method add_faux_cols - * @param {Number} cols The number of cols you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cols = function(cols) { - var actual_cols = this.cols; - var max_cols = actual_cols + (cols || 1); - max_cols = Math.min(max_cols, this.options.max_cols); - - for (var c = actual_cols + 1; c <= max_cols; c++) { - for (var r = this.rows; r >= 1; r--) { - this.add_faux_cell(r, c); - } - } - - this.cols = max_cols; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - - /** - * Recalculates the offsets for the faux grid. You need to use it when - * the browser is resized. - * - * @method recalculate_faux_grid - * @return {Object} Returns the instance of the Gridster class. - */ - fn.recalculate_faux_grid = function() { - var aw = this.$wrapper.width(); - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; - - $.each(this.faux_grid, $.proxy(function(i, coords) { - this.faux_grid[i] = coords.update({ - left: this.baseX + (coords.data.col -1) * this.min_widget_width, - top: this.baseY + (coords.data.row -1) * this.min_widget_height - }); - }, this)); - - return this; - }; - - - /** - * Get all widgets in the DOM and register them. - * - * @method get_widgets_from_DOM - * @return {Object} Returns the instance of the Gridster class. - */ - fn.get_widgets_from_DOM = function() { - var widgets_coords = this.$widgets.map($.proxy(function(i, widget) { - var $w = $(widget); - return this.dom_to_coords($w); - }, this)); - - widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); - - var changes = $(widgets_coords).map($.proxy(function(i, wgd) { - return this.register_widget(wgd) || null; - }, this)); - - if (changes.length) { - this.$el.trigger('gridster:positionschanged'); - } - - return this; - }; - - - /** - * Calculate columns and rows to be set based on the configuration - * parameters, grid dimensions, etc ... - * - * @method generate_grid_and_stylesheet - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_grid_and_stylesheet = function() { - var aw = this.$wrapper.width(); - var max_cols = this.options.max_cols; - - var cols = Math.floor(aw / this.min_widget_width) + - this.options.extra_cols; - - var actual_cols = this.$widgets.map(function() { - return $(this).attr('data-col'); - }).get(); - - //needed to pass tests with phantomjs - actual_cols.length || (actual_cols = [0]); - - var min_cols = Math.max.apply(Math, actual_cols); - - this.cols = Math.max(min_cols, cols, this.options.min_cols); - - if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { - this.cols = max_cols; - } - - // get all rows that could be occupied by the current widgets - var max_rows = this.options.extra_rows; - this.$widgets.each(function(i, w) { - max_rows += (+$(w).attr('data-sizey')); - }); - - this.rows = Math.max(max_rows, this.options.min_rows); - - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; + }; + + + /** + * checks the grid to see if the desired column is a valid row in the config + * @Param {Number} col number to check + * @Param {Number} [size_y] optional number of columns in the offset + * @Return {Boolean} true if the desire column exists in the grid. + */ + fn.is_valid_col = function (col, size_x) { + //if the grid is set to autogrow all cols are valid + if (this.options.max_cols === Infinity) { + return true; + } + return this.cols >= this.calculate_highest_col(col, size_x) ; + }; + + /** + * checks the grid to see if the desired row is a valid row in the config + * @Param {Number} row number to check + * @Param {Number} [size_y] optional number of rows in the offset + * @Return {Boolean} true if the desire row exists in the grid. + */ + fn.is_valid_row = function (row, size_y){ + return this.rows >= this.calculate_highest_row(row, size_y); + }; + + /** + * extract out the logic to calculate the highest col the widget needs + * in the grid in order to fit. Based on the current row and desired size + * @param {Number} col the column number of the current postiton of the widget + * @param {Number} [size_x] veritical size of the widget + * @returns {number} highest col needed to contain the widget + */ + fn.calculate_highest_col = function (col, size_x) { + return col + (size_x || 1) - 1; + }; + + /** + * extract out the logic to calculate the highest row the widget needs + * in the grid in order to fit. Based on the current row and desired size + * @param {Number} row the row number of the current postiton of the widget + * @param {Number} [size_y] horizontal size of the widget + * @returns {number} highest row needed to contain the widget + */ + fn.calculate_highest_row = function (row, size_y){ + return row + (size_y || 1) - 1; + }; + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function (col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function (col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + /** + * Determines if widget is supposed to be static. + * WARNING: as of 0.6.6 this feature is buggy when + * used with resizable widgets, as resizing widgets + * above and below a static widgit can cause it to move. + * This feature is considered experimental at this time + * @method is_static + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true if widget exists and has static class, + * else returns false + */ + fn.is_static = function (col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + if (cell.hasClass(this.options.static_class)) { + return true; + } + } + + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function (col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + + /** + * Get widgets overlapping with the player or with the object passed + * representing the grid cells. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function (cells) { + cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function (i, col) { + $.each(cells.rows, $.proxy(function (i, row) { + if (this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function (col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + + // Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); + } + + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; + + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.$preview_holder.attr({ + 'data-row': row, + 'data-col': col + }); + + if (this.options.shift_player_up) { + if (moved_down || changed_column) { + + var $nexts = this.widgets_below({ + col: phgd.col, + row: phgd.row, + size_y: phgd.size_y, + size_x: phgd.size_x + }); + + $nexts.each($.proxy(function (i, widget) { + //Make sure widget is at it's topmost position + var $w = $(widget); + var wgd = $w.coords().grid; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + this.move_widget_to($w, can_go_widget_up); + } + + }, this)); + } + + var $widgets_under_ph = this.get_widgets_under_player( + this.cells_occupied_by_placeholder); + + if ($widgets_under_ph.length) { + $widgets_under_ph.each($.proxy(function (i, widget) { + var $w = $(widget); + this.move_widget_down( + $w, row + phgd.size_y - $w.data('coords').grid.row); + }, this)); + } + } + + }; + + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function (widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || + this.is_widget(tcol, r) && + grid_col[r].is($widgets_under_player) + ) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + } else { + break; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function (a, b) { + return a - b; + }); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function (widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + /* generate an array with columns as index and array with topmost rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + // iterate over each row + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + } + } + + if (!this.is_player(tcol, r) && !this.is_placeholder_in(tcol, r) && !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + } + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function (a, b) { + return a - b; + }); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function (widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row) { + var common = true; + /*jshint -W083 */ + $.each(upper_rows, function (col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + /*jshint +W083 */ + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + } else { + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index( + valid_rows, size_y); + } + } + + return new_row; + }; + + + fn.get_consecutive_numbers_index = function (arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; // or null? + + for (var i = 0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + } else { + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function () { + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function (i, col) { + $.each(rows_from_bottom, $.proxy(function (i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { + return true; + } //next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && + $.inArray($w, used) === -1 + ) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function (col) { + this.set_player(col, undefined , false); + }; + + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function (row) { + this.set_player(undefined, row, false); + }; + + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function (col) { + //this.set_player(col, false); + var self = this; + if (this.options.shift_larger_widgets_down) { + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], + function (tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function (row) { + //this.set_player(false, row); + var self = this; + var cols = this.cells_occupied_by_player.cols; + if (this.options.shift_larger_widgets_down) { + /*jshint -W083 */ + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function (tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + /*jshint +W083 */ + } + }; + + //Not yet part of api - DM. + fn.new_move_widget_to = function ($widget, col, row) { + var widget_grid_data = $widget.coords().grid; + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + widget_grid_data.col = col; + + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + $widget.attr('data-col', col); + this.update_widget_position(widget_grid_data, $widget); + this.$changed = this.$changed.add($widget); + + return this; + }; + + + /** + * Move a widget to a specific row and column. + * If the widget has widgets below, all of these widgets will be moved also + * + * @method move_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @param {Number} new_col the column number to be set in widget + * @param {Number} new_row the row number to be set in widget + * @param {Function} callback is called when whole process is done. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.move_widget = function ($widget, new_col, new_row, callback) { + var wgd = $widget.coords().grid; + + var new_grid_data = { + col: new_col, + row: new_row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.col, new_grid_data.row); + } + + return $widget; + }; + + + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Gridster} Returns the instance of the Gridster Class. + * @param row - row to move the widget to + */ + fn.move_widget_to = function ($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to( + widget_grid_data, widget_grid_data.col, row); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + + $next_widgets.each(function (i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Boolean} Returns if the widget moved + */ + fn.move_widget_up = function ($widget, y_units) { + if (y_units === undefined){ + return false; + } + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + y_units || (y_units = 1); + + if (!this.can_go_up($widget)) { + return false; + } //break; + + this.for_each_column_occupied(el_grid_data, function (col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row( + widget_grid_data, col, next_row); + + if (!next_row) { + return true; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + /* $next_widgets.each($.proxy(function(i, widget) { + console.log('from_within_move_widget_up'); + this.move_widget_up($(widget), y_units); + }, this)); */ + } + }); + + }; + + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {jQuery} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} y_units The number of cells that the widget has to move. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function ($widget, y_units) { + var el_grid_data, actual_row, moved, y_diff; + + if (y_units <= 0) { + return false; + } + + el_grid_data = $widget.coords().grid; + actual_row = el_grid_data.row; + moved = []; + y_diff = y_units; + + if (!$widget) { + return false; + } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function (i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff( + wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + + widget_grid_data.row = next_row; + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + } + }; + + + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function (widget_grid_data, col, row) { + var result = true; + var urc = []; // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + urc[tcol] = []; + + r = actual_row; + while (r--) { + if (this.is_empty(tcol, r) && !this.is_placeholder_in(tcol, r) + ) { + urc[tcol].push(r); + } else { + break; + } + } + + if (!urc[tcol].length) { + result = false; + return true; + } + + }); + + if (!result) { + return false; + } + + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for (r = 1; r < actual_row; r++) { + var common = true; + + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } + + if (common === true) { + result = r; + break; + } + } + + return result; + }; + + + fn.displacement_diff = function (widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; + + this.for_each_column_occupied(widget_grid_data, function (col) { + var temp_y_units = 0; + + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; + } + } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {object} $el The jQuery wrapped HTMLElement. + * @return {jQuery} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function ($el) { + var $nexts = $([]); + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + if (el_grid_data === undefined) { + //there is no grid, so we can't calculate the widgets below + return $nexts; + } + var self = this; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + + this.for_each_column_occupied(el_grid_data, function (col) { + self.for_each_widget_below(col, next_row, function (tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; // break + } + }); + }); + + return Gridster.sort_by_row_asc($nexts); + }; + + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Gridster} Returns the instance of the Gridster Class. + * @param row + */ + fn.set_cells_player_occupies = function (col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function () { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + fn.can_go_down = function ($el) { + var can_go_down = true; + var $gr = this; + + if ($el.hasClass(this.options.static_class)) { + can_go_down = false; + } + + this.widgets_below($el).each(function () { + if ($(this).hasClass($gr.options.static_class)) { + can_go_down = false; + } + }); + + return can_go_down; + }; + + + fn.can_go_up = function ($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + + var result = true; + if (initial_row === 1) { + return false; + } + + this.for_each_column_occupied(el_grid_data, function (col) { + if (this.is_occupied(col, prev_row) || + this.is_player(col, prev_row) || + this.is_placeholder_in(col, prev_row) || + this.is_player_in(col, prev_row) + ) { + result = false; + return true; //break + } + }); + + return result; + }; + + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function (widget_grid_data, col, row) { + var $w = widget_grid_data.el; + var future_wd = { + size_y: widget_grid_data.size_y, + size_x: widget_grid_data.size_x, + col: col, + row: row + }; + var result = true; + + //Prevents widgets go out of the grid, check only if the grid is not set to autogrow + if (this.options.max_cols !== Infinity) { + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + } + } + + if (this.options.max_rows < row + widget_grid_data.size_y - 1) { + return false; + } + + this.for_each_cell_occupied(future_wd, function (tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function (from_col) { + var max = (from_col || this.player_grid_data.col) + + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function (from_row) { + var max = (from_row || this.player_grid_data.row) + + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function (el_grid_data) { + var cells = {cols: [], rows: []}; + var i; + if (arguments[1] instanceof $) { + el_grid_data = arguments[1].coords().grid; + } + + for (i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for (i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * widget. + * @param grid_data + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function (grid_data, callback) { + this.for_each_column_occupied(grid_data, function (col) { + this.for_each_row_occupied(grid_data, function (row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function (el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function (el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + fn.clean_up_changed = function () { + var $gr = this; + $gr.$changed.each(function () { + if ($gr.options.shift_larger_widgets_down) { + $gr.move_widget_up($(this)); + } + }); + }; + + + /** + * Traverse a series of widgets + * @param type - currently supported 'for_each' + * @param direction - The direction to traverse. Supports 'above' and 'below' + * NOTE: the directions are based on the layout in the grid above is toward the top + * and below is toward the bottom. EG opposite direction of the row numbers + * @param col - column to traverse + * @param row - starting row in the column + * @param callback - a function that will be called for every widget found + * @private + */ + fn._traversing_widgets = function (type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { + return; + } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof $) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + + var methods = { + 'for_each/above': function () { + while (trow--) { + if (trow > 0 && this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { + break; + } + } + } + }, + 'for_each/below': function () { + for (trow = row + 1, max = ga[col].length; trow < max; trow++) { + if (this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + //break was causing problems, leaving for testing. + //if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function (col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function (col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function () { + var r; + var gm = this.gridmap; + var rl = gm[1].length; + var rows = [], cols = []; + for (var c = gm.length - 1; c >= 1; c--) { + for (r = rl - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + cols.push(c); + break; + } + } + } + + return { + col: Math.max.apply(Math, cols), + row: Math.max.apply(Math, rows) + }; + }; + + /** + * return the widgets what exist within the given range of grid cells + * @param col1 - col of upper left search + * @param row1 - row of upper left search + * @param col2 - col of lower right search + * @param row2 - row of lower right search + * @returns {*|jQuery|HTMLElement} - a collection of the cells within the range + */ + fn.get_widgets_in_range = function (col1, row1, col2, row2) { + var $widgets = $([]); + var c, r, $w, wgd; + + for (c = col2; c >= col1; c--) { + for (r = row2; r >= row1; r--) { + $w = this.is_widget(c, r); + + if ($w !== false) { + wgd = $w.data('coords').grid; + if (wgd.col >= col1 && wgd.col <= col2 && + wgd.row >= row1 && wgd.row <= row2 + ) { + $widgets = $widgets.add($w); + } + } + } + } + + return $widgets; + }; + + /** + * return any widget which is located at the given coordinates + * @param col - col to search at + * @param row - row to search at + * @returns {*} - a collection of any widgets found. + */ + fn.get_widgets_at_cell = function (col, row) { + return this.get_widgets_in_range(col, row, col, row); + }; + + /** + * gets the list of widgets in either the row or the col passed in_loop + * Not sure if this makes sense for the API or what the use case is, + * but I'm documenting it as it exists. + * @param col - a col to search for widgets from + * @param row - a row to search for widgets from + * @returns {*|jQuery|HTMLElement} - a collection of the widgets in either + * the row or the col passed in. + * + * @deprecated - if you want to search for widgets in a cell or a range + * look at get_widgets_in_range and get_widgets_at_cell + */ + fn.get_widgets_from = function (col, row) { + var $widgets = $(); + + if (col) { + $widgets = $widgets.add( + this.$widgets.filter(function () { + var tcol = parseInt($(this).attr('data-col')); + return (tcol === col || tcol > col); + }) + ); + } + + if (row) { + $widgets = $widgets.add( + this.$widgets.filter(function () { + var trow = parseInt($(this).attr('data-row')); + return (trow === row || trow > row); + }) + ); + } + + return $widgets; + }; + + + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function (height) { + if (typeof height === 'undefined') { + var r = this.get_highest_occupied_cell().row; + height = ((r + 1) * this.options.widget_margins[1]) + (r * this.min_widget_height); + } + + this.container_height = height; + this.$el.css('height', this.container_height); + return this; + }; + + /** + * Set the current width of the parent grid. + * + * @method set_dom_grid_width + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_width = function (cols) { + if (typeof cols === 'undefined') { + cols = this.get_highest_occupied_cell().col; + } + + var max_cols = (this.options.max_cols === Infinity ? this.options.max_cols : this.cols); + + cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); + this.container_width = ((cols + 1) * this.options.widget_margins[0]) + (cols * this.min_widget_width); + if (this.is_responsive()) { + this.$el.css({'min-width': '100%', 'max-width': '100%'}); + return this; //if we are responsive exit before setting the width of $el + } + this.$el.css('width', this.container_width); + + return this; + }; + + + /** + * Checks if this grid is responsive. + * autogenerate_stylesheet be true, the widget base width should be auto, and there must be a max_cols set. + * @returns {Boolean} + */ + fn.is_responsive = function () { + return this.options.autogenerate_stylesheet && this.options.widget_base_dimensions[0] === 'auto' && this.options.max_cols !== Infinity; + }; + + /** + * Generates the width of the grid columns based on the width of the window. + * @returns {number} + */ + fn.get_responsive_col_width = function () { + var cols = this.cols || this.options.max_cols; + return (this.$el[0].scrollWidth - ((cols + 1) * this.options.widget_margins[0])) / cols; + }; + + /** + * Changes the minimum width of a widget based on the width of the window and the number of cols that can + * fit in it. + * @returns {Gridster} + */ + fn.resize_responsive_layout = function () { + this.min_widget_width = this.get_responsive_col_width(); + this.generate_stylesheet(); + this.update_widgets_dimensions(); + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + return this; + }; + + /** + * Switches between collapsed widgets the span the full width when the responsive_breakpoint is triggered. + * @param collapse + * @param opts + * @returns {Gridster} + */ + fn.toggle_collapsed_grid = function (collapse, opts) { + if (collapse) { + this.$widgets.css({ + 'margin-top': opts.widget_margins[0], + 'margin-bottom': opts.widget_margins[0], + 'min-height': opts.widget_base_dimensions[1] + }); + + this.$el.addClass('collapsed'); + + if (this.resize_api) { + this.disable_resize(); + } + + if (this.drag_api) { + this.disable(); + } + } else { + this.$widgets.css({ + 'margin-top': 'auto', + 'margin-bottom': 'auto', + 'min-height': 'auto' + }); + this.$el.removeClass('collapsed'); + if (this.resize_api) { + this.enable_resize(); + } + + if (this.drag_api) { + this.enable(); + } + } + return this; + }; + + /** + * It generates the necessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {object} [opts] - set of gridster config options to generate stylessheets based on + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function (opts) { + var styles = ''; + var i; + var full_width = this.is_responsive() && this.options.responsive_breakpoint && ($(window).width() < this.options.responsive_breakpoint); + + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = this.options.namespace); + opts.widget_base_dimensions || + (opts.widget_base_dimensions = this.options.widget_base_dimensions); + + opts.widget_margins || (opts.widget_margins = this.options.widget_margins); + + if (this.is_responsive()) { + opts.widget_base_dimensions = [this.get_responsive_col_width(), opts.widget_base_dimensions[1]]; + this.toggle_collapsed_grid(full_width, opts); + } + + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + } + + this.generated_stylesheets.push(serialized_opts); + Gridster.generated_stylesheets.push(serialized_opts); + + /* generate CSS styles for cols */ + for (i = 1; i <= opts.cols + 1; i++) { + styles += (opts.namespace + ' [data-col="' + i + '"] { left:' + + (full_width ? this.options.widget_margins[0] : + ((i * opts.widget_margins[0]) + ((i - 1) * opts.widget_base_dimensions[0]))) + 'px; }\n'); + } + + /* generate CSS styles for rows */ + for (i = 1; i <= opts.rows + 1; i++) { + styles += (opts.namespace + ' [data-row="' + i + '"] { top:' + + ((i * opts.widget_margins[1]) + ((i - 1) * opts.widget_base_dimensions[1])) + 'px; }\n'); + } + + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + + (full_width ? 'auto' : ((y * opts.widget_base_dimensions[1]) + ((y - 1) * opts.widget_margins[1]))) + (full_width ? '' : 'px') + '; }\n'); + + } + + for (var x = 1; x <= opts.cols; x++) { + var colWidth = ((x * opts.widget_base_dimensions[0]) + ((x - 1) * opts.widget_margins[0])); + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + + (full_width ? (this.$wrapper.width() - this.options.widget_margins[0] * 2) : colWidth > this.$wrapper.width() ? this.$wrapper.width() : colWidth ) + 'px; }\n'); + + } + + this.remove_style_tags(); + + return this.add_style_tag(styles); + }; + + + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function (css) { + var d = document; + var cssID = 'gridster-stylesheet'; + if (this.options.namespace !== '') { + cssID = cssID + '-' + this.options.namespace; + } + + if (!document.getElementById(cssID)) { + var tag = d.createElement('style'); + tag.id = cssID; + + d.getElementsByTagName('head')[0].appendChild(tag); + tag.setAttribute('type', 'text/css'); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + } else { + tag.appendChild(document.createTextNode(css)); + } + + this.remove_style_tags(); + this.$style_tags = this.$style_tags.add(tag); + } + + return this; + }; + + + /** + * Remove the style tag with the associated id from the head of the document + * + * @method remove_style_tag + * @return {Object} Returns the instance of the Gridster class. + */ + fn.remove_style_tags = function () { + var all_styles = Gridster.generated_stylesheets; + var ins_styles = this.generated_stylesheets; + + this.$style_tags.remove(); + + Gridster.generated_stylesheets = $.map(all_styles, function (s) { + if ($.inArray(s, ins_styles) === -1) { + return s; + } + }); + }; + + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function (rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for (col = cols; col > 0; col--) { + this.gridmap[col] = []; + for (row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function (row, col) { + var coords = $({ + left: this.baseX + ((col - 1) * this.min_widget_width), + top: this.baseY + (row - 1) * this.min_widget_height, + width: this.min_widget_width, + height: this.min_widget_height, + col: col, + row: row, + original_col: col, + original_row: row + }).coords(); + + if (!$.isArray(this.gridmap[col])) { + this.gridmap[col] = []; + } + + if (typeof this.gridmap[col][row] === 'undefined') { + this.gridmap[col][row] = false; + } + this.faux_grid.push(coords); + + return this; + }; + + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function (rows) { + rows = window.parseInt(rows, 10); + + var actual_rows = this.rows; + var max_rows = actual_rows + parseInt(rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + } + } + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Add cols to the faux grid. + * + * @method add_faux_cols + * @param {Number} cols The number of cols you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cols = function (cols) { + cols = window.parseInt(cols, 10); + + var actual_cols = this.cols; + var max_cols = actual_cols + parseInt(cols || 1); + max_cols = Math.min(max_cols, this.options.max_cols); + + for (var c = actual_cols + 1; c <= max_cols; c++) { + for (var r = this.rows; r >= 1; r--) { + this.add_faux_cell(r, c); + } + } + + this.cols = max_cols; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function () { + var aw = this.$wrapper.width(); + this.baseX = ($window.width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.$wrapper.css('position') === 'relative') { + this.baseX = this.baseY = 0; + } + + $.each(this.faux_grid, $.proxy(function (i, coords) { + this.faux_grid[i] = coords.update({ + left: this.baseX + (coords.data.col - 1) * this.min_widget_width, + top: this.baseY + (coords.data.row - 1) * this.min_widget_height + }); + }, this)); + + if (this.is_responsive()) { + this.resize_responsive_layout(); + } + + if (this.options.center_widgets) { + this.center_widgets(); + } + + return this; + }; + + + /** + * Resize dimensions of widgets in grid based on given options + * + * @method resize_widget_dimensions + * @param options + * @returns {Gridster} + */ + fn.resize_widget_dimensions = function (options) { + if (options.widget_margins) { + this.options.widget_margins = options.widget_margins; + } + + if (options.widget_base_dimensions) { + this.options.widget_base_dimensions = options.widget_base_dimensions; + } + + this.$widgets.each($.proxy(function (i, widget) { + var $widget = $(widget); + this.resize_widget($widget); + }, this)); + + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + return this; + }; + + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function () { + var widgets_coords = this.$widgets.map($.proxy(function (i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + var changes = $(widgets_coords).map($.proxy(function (i, wgd) { + return this.register_widget(wgd) || null; + }, this)); + + if (changes.length) { + this.$el.trigger('gridster:positionschanged'); + } + + return this; + }; + + fn.get_num_widgets = function () { + return this.$widgets.size(); + }; + + /** + * Helper function used to set the current number of columns in the grid + * + * @param wrapper_width + */ + fn.set_num_columns = function (wrapper_width) { + + var max_cols = this.options.max_cols; + + var cols = Math.floor(wrapper_width / (this.min_widget_width + this.options.widget_margins[0])) + + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function () { + return $(this).attr('data-col'); + }).get(); + + //needed to pass tests with phantomjs + actual_cols.length || (actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + this.cols = Math.max(min_cols, cols, this.options.min_cols); + + if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { + this.cols = max_cols; + } + + if (this.drag_api) { + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + }; + + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function () { + var aw = this.$wrapper.width(); + + this.set_num_columns(aw); + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function (i, w) { + max_rows += (+$(w).attr('data-sizey')); + }); + + //this.rows = Math.max(max_rows, this.options.min_rows); + this.rows = this.options.max_rows; + + this.baseX = ($window.width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this.generate_faux_grid(this.rows, this.cols); + }; + + /** + * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks + * + * @method destroy + * @param {Boolean} remove If true, remove gridster from DOM. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.destroy = function (remove) { + this.$el.removeData('gridster'); + + // remove coords from elements + $.each(this.$widgets, function () { + $(this).removeData('coords'); + }); - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this.generate_faux_grid(this.rows, this.cols); - }; - - /** - * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks - * - * @method destroy - * @param {Boolean} remove If true, remove gridster from DOM. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.destroy = function(remove) { - this.$el.removeData('gridster'); - - // remove bound callback on window resize - $(window).unbind('.gridster'); + // remove bound callback on window resize + $window.unbind('.gridster'); - if (this.drag_api) { - this.drag_api.destroy(); - } + if (this.drag_api) { + this.drag_api.destroy(); + } + if (this.resize_api) { + this.resize_api.destroy(); + } - this.remove_style_tags(); + this.$widgets.each(function (i, el) { + $(el).coords().destroy(); + }); - remove && this.$el.remove(); + if (this.resize_api) { + this.resize_api.destroy(); + } + + this.remove_style_tags(); - return this; - }; + remove && this.$el.remove(); + + return this; + }; - //jQuery adapter - $.fn.gridster = function(options) { - return this.each(function() { - if (! $(this).data('gridster')) { - $(this).data('gridster', new Gridster( this, options )); - } - }); - }; + //jQuery adapter + $.fn.gridster = function (options) { + return this.each(function () { + var $this = $(this); + if (!$this.data('gridster')) { + $this.data('gridster', new Gridster(this, options)); + } + }); + }; - return Gridster; + return Gridster; })); diff --git a/lib/gridster/dist/jquery.gridster.min.css b/lib/gridster/dist/jquery.gridster.min.css index 9d9fe8445..a78051fdf 100644 --- a/lib/gridster/dist/jquery.gridster.min.css +++ b/lib/gridster/dist/jquery.gridster.min.css @@ -1,2 +1,2 @@ -/*! gridster.js - v0.5.6 - 2014-09-25 - * http://gridster.net/ - Copyright (c) 2014 ducksboard; Licensed MIT */ -.gridster{position:relative}.gridster>*{margin:0 auto;-webkit-transition:height .4s,width .4s;-moz-transition:height .4s,width .4s;-o-transition:height .4s,width .4s;-ms-transition:height .4s,width .4s;transition:height .4s,width .4s}.gridster .gs-w{z-index:2;position:absolute}.ready .gs-w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s;-moz-transition:opacity .3s,left .3s,top .3s;-o-transition:opacity .3s,left .3s,top .3s;transition:opacity .3s,left .3s,top .3s}.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder{-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster .dragging,.gridster .resizing{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important}.gs-resize-handle{position:absolute;z-index:1}.gs-resize-handle-both{width:20px;height:20px;bottom:-8px;right:-8px;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=);background-position:top left;background-repeat:no-repeat;cursor:se-resize;z-index:20}.gs-resize-handle-x{top:0;bottom:13px;right:-5px;width:10px;cursor:e-resize}.gs-resize-handle-y{left:0;right:13px;bottom:-5px;height:10px;cursor:s-resize}.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle{opacity:1}.gs-resize-handle,.gs-w.dragging .gs-resize-handle{opacity:0}.gs-resize-disabled .gs-resize-handle{display:none!important}[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle{display:none!important} \ No newline at end of file +/*! gridster.js - v0.6.10 - 2015-08-05 - * https://dsmorse.github.io/gridster.js/ - Copyright (c) 2015 ducksboard; Licensed MIT */ +.gridster{position:relative}.gridster>*{-webkit-transition:height .4s,width .4s;-moz-transition:height .4s,width .4s;-o-transition:height .4s,width .4s;-ms-transition:height .4s,width .4s;transition:height .4s,width .4s}.gridster .gs-w{z-index:2;position:absolute}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster.collapsed{height:auto!important}.gridster.collapsed .gs-w{position:static!important}.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder{-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .dragging,.gridster .resizing{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important}.gs-resize-handle{position:absolute;z-index:1}.gs-resize-handle-both{width:20px;height:20px;bottom:-8px;right:-8px;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=);background-position:top left;background-repeat:no-repeat;cursor:se-resize;z-index:20}.gs-resize-handle-x{top:0;bottom:13px;right:-5px;width:10px;cursor:e-resize}.gs-resize-handle-y{left:0;right:13px;bottom:-5px;height:10px;cursor:s-resize}.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle{opacity:1}.gs-resize-handle,.gs-w.dragging .gs-resize-handle{opacity:0}.gs-resize-disabled .gs-resize-handle,[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle{display:none!important} \ No newline at end of file diff --git a/lib/gridster/dist/jquery.gridster.min.js b/lib/gridster/dist/jquery.gridster.min.js index a256947a2..5e0fbf530 100644 --- a/lib/gridster/dist/jquery.gridster.min.js +++ b/lib/gridster/dist/jquery.gridster.min.js @@ -1,2 +1,2 @@ -/*! gridster.js - v0.5.6 - 2014-09-25 - * http://gridster.net/ - Copyright (c) 2014 ducksboard; Licensed MIT */ (function(t,i){"function"==typeof define&&define.amd?define("gridster-coords",["jquery"],i):t.GridsterCoords=i(t.$||t.jQuery)})(this,function(t){function i(i){return i[0]&&t.isPlainObject(i[0])?this.data=i[0]:this.el=i,this.isCoords=!0,this.coords={},this.init(),this}var e=i.prototype;return e.init=function(){this.set(),this.original_coords=this.get()},e.set=function(t,i){var e=this.el;if(e&&!t&&(this.data=e.offset(),this.data.width=e.width(),this.data.height=e.height()),e&&t&&!i){var s=e.offset();this.data.top=s.top,this.data.left=s.left}var r=this.data;return r.left===void 0&&(r.left=r.x1),r.top===void 0&&(r.top=r.y1),this.coords.x1=r.left,this.coords.y1=r.top,this.coords.x2=r.left+r.width,this.coords.y2=r.top+r.height,this.coords.cx=r.left+r.width/2,this.coords.cy=r.top+r.height/2,this.coords.width=r.width,this.coords.height=r.height,this.coords.el=e||!1,this},e.update=function(i){if(!i&&!this.el)return this;if(i){var e=t.extend({},this.data,i);return this.data=e,this.set(!0,!0)}return this.set(!0),this},e.get=function(){return this.coords},e.destroy=function(){this.el.removeData("coords"),delete this.el},t.fn.coords=function(){if(this.data("coords"))return this.data("coords");var t=new i(this,arguments[0]);return this.data("coords",t),t},i}),function(t,i){"function"==typeof define&&define.amd?define("gridster-collision",["jquery","gridster-coords"],i):t.GridsterCollision=i(t.$||t.jQuery,t.GridsterCoords)}(this,function(t){function i(i,s,r){this.options=t.extend(e,r),this.$element=i,this.last_colliders=[],this.last_colliders_coords=[],this.set_colliders(s),this.init()}var e={colliders_context:document.body,overlapping_region:"C"};i.defaults=e;var s=i.prototype;return s.init=function(){this.find_collisions()},s.overlaps=function(t,i){var e=!1,s=!1;return(i.x1>=t.x1&&i.x1<=t.x2||i.x2>=t.x1&&i.x2<=t.x2||t.x1>=i.x1&&t.x2<=i.x2)&&(e=!0),(i.y1>=t.y1&&i.y1<=t.y2||i.y2>=t.y1&&i.y2<=t.y2||t.y1>=i.y1&&t.y2<=i.y2)&&(s=!0),e&&s},s.detect_overlapping_region=function(t,i){var e="",s="";return t.y1>i.cy&&t.y1i.y1&&t.y2i.cx&&t.x1i.x1&&t.x2o;o++)-1===t.inArray(r[o],i)&&e.call(this,r[o]);for(var n=0,h=i.length;h>n;n++)-1===t.inArray(i[n],r)&&s.call(this,i[n])},s.find_collisions=function(i){for(var e=this,s=this.options.overlapping_region,r=[],o=[],a=this.colliders||this.$colliders,n=a.length,h=e.$element.coords().update(i||!1).get();n--;){var _=e.$colliders?t(a[n]):a[n],d=_.isCoords?_:_.coords(),l=d.get(),c=e.overlaps(h,l);if(c){var p=e.detect_overlapping_region(h,l);if(p===s||"all"===s){var g=e.calculate_overlapped_area_coords(h,l),u=e.calculate_overlapped_area(g),f={area:u,area_coords:g,region:p,coords:l,player_coords:h,el:_};e.options.on_overlap&&e.options.on_overlap.call(this,f),r.push(d),o.push(f)}}}return(e.options.on_overlap_stop||e.options.on_overlap_start)&&this.manage_colliders_start_stop(r,e.options.on_overlap_start,e.options.on_overlap_stop),this.last_colliders_coords=r,o},s.get_closest_colliders=function(t){var i=this.find_collisions(t);return i.sort(function(t,i){return"C"===t.region&&"C"===i.region?t.coords.y1this.player_max_left?o=this.player_max_left:this.player_min_left>o&&(o=this.player_min_left)),{position:{left:o,top:a},pointer:{left:e.left,top:e.top,diff_left:s+(t(window).scrollLeft()-this.win_offset_x),diff_top:r+(t(window).scrollTop()-this.win_offset_y)}}},_.get_drag_data=function(t){var i=this.get_offset(t);return i.$player=this.$player,i.$helper=this.helper?this.$helper:this.$player,i},_.set_limits=function(t){return t||(t=this.$container.width()),this.player_max_left=t-this.player_width+-this.options.offset_left,this.options.container_width=t,this},_.scroll_in=function(i,e){var o,n=r[i],h=50,_=30,d="x"===i,l=d?this.window_width:this.window_height,c=d?t(document).width():t(document).height(),p=d?this.$player.width():this.$player.height(),g=s["scroll"+a(n)](),u=g,f=u+l,w=f-h,m=u+h,y=u+e.pointer[n],v=c-l+p;return y>=w&&(o=g+_,v>o&&(s["scroll"+a(n)](o),this["scroll_offset_"+i]+=_)),m>=y&&(o=g-_,o>0&&(s["scroll"+a(n)](o),this["scroll_offset_"+i]-=_)),this},_.manage_scroll=function(t){this.scroll_in("x",t),this.scroll_in("y",t)},_.calculate_dimensions=function(){this.window_height=s.height(),this.window_width=s.width()},_.drag_handler=function(i){if(i.target.nodeName,!this.disabled&&(1===i.which||o)&&!this.ignore_drag(i)){var e=this,s=!0;return this.$player=t(i.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(i),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.$document.on(this.pointer_events.move,function(t){var i=e.get_mouse_pos(t),r=Math.abs(i.left-e.mouse_init_pos.left),o=Math.abs(i.top-e.mouse_init_pos.top);return r>e.options.distance||o>e.options.distance?s?(s=!1,e.on_dragstart.call(e,t),!1):(e.is_dragging===!0&&e.on_dragmove.call(e,t),!1):!1}),o?void 0:!1}},_.on_dragstart=function(i){if(i.preventDefault(),this.is_dragging)return this;this.drag_start=this.is_dragging=!0;var e=this.$container.offset();return this.baseX=Math.round(e.left),this.baseY=Math.round(e.top),this.initial_container_width=this.options.container_width||this.$container.width(),"clone"===this.options.helper?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.win_offset_y=t(window).scrollTop(),this.win_offset_x=t(window).scrollLeft(),this.scroll_offset_y=0,this.scroll_offset_x=0,this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.player_height=this.$player.height(),this.set_limits(this.options.container_width),this.options.start&&this.options.start.call(this.$player,i,this.get_drag_data(i)),!1},_.on_dragmove=function(t){var i=this.get_drag_data(t);this.options.autoscroll&&this.manage_scroll(i),this.options.move_element&&(this.helper?this.$helper:this.$player).css({position:"absolute",left:i.position.left,top:i.position.top});var e=this.last_position||i.position;return i.prev_position=e,this.options.drag&&this.options.drag.call(this.$player,t,i),this.last_position=i.position,!1},_.on_dragstop=function(t){var i=this.get_drag_data(t);return this.drag_start=!1,this.options.stop&&this.options.stop.call(this.$player,t,i),this.helper&&this.options.remove_helper&&this.$helper.remove(),!1},_.on_select_start=function(t){return this.disabled||this.ignore_drag(t)?void 0:!1},_.enable=function(){this.disabled=!1},_.disable=function(){this.disabled=!0},_.destroy=function(){this.disable(),this.$container.off(this.ns),this.$document.off(this.ns),t(window).off(this.ns),t.removeData(this.$container,"drag")},_.ignore_drag=function(i){return this.options.handle?!t(i.target).is(this.options.handle):t.isFunction(this.options.ignore_dragging)?this.options.ignore_dragging(i):t(i.target).is(this.options.ignore_dragging.join(", "))},t.fn.drag=function(t){return new i(this,t)},i}),function(t,i){"function"==typeof define&&define.amd?define(["jquery","gridster-draggable","gridster-collision"],i):t.Gridster=i(t.$||t.jQuery,t.GridsterDraggable,t.GridsterCollision)}(this,function(t,i){function e(i,e){this.options=t.extend(!0,{},s,e),this.$el=t(i),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs-w"),this.widgets=[],this.$changed=t([]),this.wrapper_width=this.$wrapper.width(),this.min_widget_width=2*this.options.widget_margins[0]+this.options.widget_base_dimensions[0],this.min_widget_height=2*this.options.widget_margins[1]+this.options.widget_base_dimensions[1],this.generated_stylesheets=[],this.$style_tags=t([]),this.options.auto_init&&this.init()}var s={namespace:"",widget_selector:"li",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,max_cols:1/0,min_rows:15,max_size_x:!1,autogrow_cols:!1,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,auto_init:!0,serialize_params:function(t,i){return{col:i.col,row:i.row,size_x:i.size_x,size_y:i.size_y}},collision:{},draggable:{items:".gs-w",distance:4,ignore_dragging:i.defaults.ignore_dragging.slice(0)},resize:{enabled:!1,axes:["both"],handle_append_to:"",handle_class:"gs-resize-handle",max_size:[1/0,1/0],min_size:[1,1]}};e.defaults=s,e.generated_stylesheets=[],e.sort_by_row_asc=function(i){return i=i.sort(function(i,e){return i.row||(i=t(i).coords().grid,e=t(e).coords().grid),i.row>e.row?1:-1})},e.sort_by_row_and_col_asc=function(t){return t=t.sort(function(t,i){return t.row>i.row||t.row===i.row&&t.col>i.col?1:-1})},e.sort_by_col_asc=function(t){return t=t.sort(function(t,i){return t.col>i.col?1:-1})},e.sort_by_row_desc=function(t){return t=t.sort(function(t,i){return t.row+t.size_yn&&this.add_faux_rows(Math.max(e-n,0));var d=o+i-1;d>this.cols&&this.add_faux_cols(d-this.cols);var l={col:_,row:r.row,size_x:i,size_y:e};return this.mutate_widget_in_gridmap(t,r,l),this.set_dom_grid_height(),this.set_dom_grid_width(),s&&s.call(this,l.size_x,l.size_y),t},r.mutate_widget_in_gridmap=function(i,e,s){e.size_x;var r=e.size_y,o=this.get_cells_occupied(e),a=this.get_cells_occupied(s),n=[];t.each(o.cols,function(i,e){-1===t.inArray(e,a.cols)&&n.push(e)});var h=[];t.each(a.cols,function(i,e){-1===t.inArray(e,o.cols)&&h.push(e)});var _=[];t.each(o.rows,function(i,e){-1===t.inArray(e,a.rows)&&_.push(e)});var d=[];if(t.each(a.rows,function(i,e){-1===t.inArray(e,o.rows)&&d.push(e)}),this.remove_from_gridmap(e),h.length){var l=[s.col,s.row,s.size_x,Math.min(r,s.size_y),i];this.empty_cells.apply(this,l)}if(d.length){var c=[s.col,s.row,s.size_x,s.size_y,i];this.empty_cells.apply(this,c)}if(e.col=s.col,e.row=s.row,e.size_x=s.size_x,e.size_y=s.size_y,this.add_to_gridmap(s,i),i.removeClass("player-revert"),i.data("coords").update({width:s.size_x*this.options.widget_base_dimensions[0]+2*(s.size_x-1)*this.options.widget_margins[0],height:s.size_y*this.options.widget_base_dimensions[1]+2*(s.size_y-1)*this.options.widget_margins[1]}),i.attr({"data-col":s.col,"data-row":s.row,"data-sizex":s.size_x,"data-sizey":s.size_y}),n.length){var p=[n[0],s.row,n.length,Math.min(r,s.size_y),i];this.remove_empty_cells.apply(this,p)}if(_.length){var g=[s.col,s.row,s.size_x,s.size_y,i];this.remove_empty_cells.apply(this,g)}return this.move_widget_up(i),this},r.empty_cells=function(i,e,s,r,o){var a=this.widgets_below({col:i,row:e-r,size_x:s,size_y:r});return a.not(o).each(t.proxy(function(i,s){var o=t(s).coords().grid;if(e+r-1>=o.row){var a=e+r-o.row;this.move_widget_down(t(s),a)}},this)),this.set_dom_grid_height(),this},r.remove_empty_cells=function(i,e,s,r,o){var a=this.widgets_below({col:i,row:e,size_x:s,size_y:r});return a.not(o).each(t.proxy(function(i,e){this.move_widget_up(t(e),r)},this)),this.set_dom_grid_height(),this},r.next_position=function(t,i){t||(t=1),i||(i=1);for(var s,r=this.gridmap,o=r.length,a=[],n=1;o>n;n++){s=r[n].length;for(var h=1;s>=h;h++){var _=this.can_move_to({size_x:t,size_y:i},n,h);_&&a.push({col:n,row:h,size_y:i,size_x:t})}}return a.length?e.sort_by_row_and_col_asc(a)[0]:!1},r.remove_widget=function(i,e,s){var r=i instanceof t?i:t(i),o=r.coords().grid;t.isFunction(e)&&(s=e,e=!1),this.cells_occupied_by_placeholder={},this.$widgets=this.$widgets.not(r);var a=this.widgets_below(r);return this.remove_from_gridmap(o),r.fadeOut(t.proxy(function(){r.remove(),e||a.each(t.proxy(function(i,e){this.move_widget_up(t(e),o.size_y)},this)),this.set_dom_grid_height(),s&&s.call(this,i)},this)),this},r.remove_all_widgets=function(i){return this.$widgets.each(t.proxy(function(t,e){this.remove_widget(e,!0,i)},this)),this},r.serialize=function(i){return i||(i=this.$widgets),i.map(t.proxy(function(i,e){var s=t(e);return this.options.serialize_params(s,s.coords().grid)},this)).get()},r.serialize_changed=function(){return this.serialize(this.$changed)},r.dom_to_coords=function(t){return{col:parseInt(t.attr("data-col"),10),row:parseInt(t.attr("data-row"),10),size_x:parseInt(t.attr("data-sizex"),10)||1,size_y:parseInt(t.attr("data-sizey"),10)||1,max_size_x:parseInt(t.attr("data-max-sizex"),10)||!1,max_size_y:parseInt(t.attr("data-max-sizey"),10)||!1,min_size_x:parseInt(t.attr("data-min-sizex"),10)||!1,min_size_y:parseInt(t.attr("data-min-sizey"),10)||!1,el:t}},r.register_widget=function(i){var e=i instanceof jQuery,s=e?this.dom_to_coords(i):i,r=!1;e||(i=s.el);var o=this.can_go_widget_up(s);return o&&(s.row=o,i.attr("data-row",o),this.$el.trigger("gridster:positionchanged",[s]),r=!0),this.options.avoid_overlapped_widgets&&!this.can_move_to({size_x:s.size_x,size_y:s.size_y},s.col,s.row)&&(t.extend(s,this.next_position(s.size_x,s.size_y)),i.attr({"data-col":s.col,"data-row":s.row,"data-sizex":s.size_x,"data-sizey":s.size_y}),r=!0),i.data("coords",i.coords()),i.data("coords").grid=s,this.add_to_gridmap(s,i),this.options.resize.enabled&&this.add_resize_handle(i),r},r.update_widget_position=function(t,i){return this.for_each_cell_occupied(t,function(t,e){return this.gridmap[t]?(this.gridmap[t][e]=i,void 0):this}),this},r.remove_from_gridmap=function(t){return this.update_widget_position(t,!1)},r.add_to_gridmap=function(i,e){if(this.update_widget_position(i,e||i.el),i.el){var s=this.widgets_below(i.el);s.each(t.proxy(function(i,e){this.move_widget_up(t(e))},this))}},r.draggable=function(){var i=this,e=t.extend(!0,{},this.options.draggable,{offset_left:this.options.widget_margins[0],offset_top:this.options.widget_margins[1],container_width:this.cols*this.min_widget_width,limit:!0,start:function(e,s){i.$widgets.filter(".player-revert").removeClass("player-revert"),i.$player=t(this),i.$helper=t(s.$helper),i.helper=!i.$helper.is(i.$player),i.on_start_drag.call(i,e,s),i.$el.trigger("gridster:dragstart")},stop:function(t,e){i.on_stop_drag.call(i,t,e),i.$el.trigger("gridster:dragstop")},drag:throttle(function(t,e){i.on_drag.call(i,t,e),i.$el.trigger("gridster:drag")},60)});return this.drag_api=this.$el.drag(e),this},r.resizable=function(){return this.resize_api=this.$el.drag({items:"."+this.options.resize.handle_class,offset_left:this.options.widget_margins[0],container_width:this.container_width,move_element:!1,resize:!0,limit:this.options.autogrow_cols?!1:!0,start:t.proxy(this.on_start_resize,this),stop:t.proxy(function(i,e){delay(t.proxy(function(){this.on_stop_resize(i,e)},this),120)},this),drag:throttle(t.proxy(this.on_resize,this),60)}),this},r.setup_resize=function(){this.resize_handle_class=this.options.resize.handle_class;var i=this.options.resize.axes,e='';return this.resize_handle_tpl=t.map(i,function(t){return e.replace("{type}",t)}).join(""),t.isArray(this.options.draggable.ignore_dragging)&&this.options.draggable.ignore_dragging.push("."+this.resize_handle_class),this},r.on_start_drag=function(i,e){this.$helper.add(this.$player).add(this.$wrapper).addClass("dragging"),this.highest_col=this.get_highest_occupied_cell().col,this.$player.addClass("player"),this.player_grid_data=this.$player.coords().grid,this.placeholder_grid_data=t.extend({},this.player_grid_data),this.set_dom_grid_height(this.$el.height()+this.player_grid_data.size_y*this.min_widget_height),this.set_dom_grid_width(this.cols);var s=this.player_grid_data.size_x,r=this.cols-this.highest_col;this.options.autogrow_cols&&s>=r&&this.add_faux_cols(Math.min(s-r,1));var o=this.faux_grid,a=this.$player.data("coords").coords;this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.last_cols=[],this.last_rows=[],this.collision_api=this.$helper.collision(o,this.options.collision),this.$preview_holder=t("<"+this.$player.get(0).tagName+" />",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:a.width,height:a.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,i,e)},r.on_drag=function(t,i){if(null===this.$player)return!1;var e={left:i.position.left+this.baseX,top:i.position.top+this.baseY};if(this.options.autogrow_cols){var s=this.placeholder_grid_data.col+this.placeholder_grid_data.size_x-1;s>=this.cols-1&&this.options.max_cols>=this.cols+1&&(this.add_faux_cols(1),this.set_dom_grid_width(this.cols+1),this.drag_api.set_limits(this.container_width)),this.collision_api.set_colliders(this.faux_grid)}this.colliders_data=this.collision_api.get_closest_colliders(e),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:i.position.left,top:i.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,t,i)},r.on_stop_drag=function(t,i){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging"),i.position.left=i.position.left+this.baseX,i.position.top=i.position.top+this.baseY,this.colliders_data=this.collision_api.get_closest_colliders(i.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$player.addClass("player-revert").removeClass("player").attr({"data-col":this.placeholder_grid_data.col,"data-row":this.placeholder_grid_data.row}).css({left:"",top:""}),this.$changed=this.$changed.add(this.$player),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data),this.set_cells_player_occupies(this.placeholder_grid_data.col,this.placeholder_grid_data.row),this.$player.coords().grid.row=this.placeholder_grid_data.row,this.$player.coords().grid.col=this.placeholder_grid_data.col,this.options.draggable.stop&&this.options.draggable.stop.call(this,t,i),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.set_dom_grid_height(),this.set_dom_grid_width(),this.options.autogrow_cols&&this.drag_api.set_limits(this.cols*this.min_widget_width)},r.on_start_resize=function(i,e){this.$resized_widget=e.$player.closest(".gs-w"),this.resize_coords=this.$resized_widget.coords(),this.resize_wgd=this.resize_coords.grid,this.resize_initial_width=this.resize_coords.coords.width,this.resize_initial_height=this.resize_coords.coords.height,this.resize_initial_sizex=this.resize_coords.grid.size_x,this.resize_initial_sizey=this.resize_coords.grid.size_y,this.resize_initial_col=this.resize_coords.grid.col,this.resize_last_sizex=this.resize_initial_sizex,this.resize_last_sizey=this.resize_initial_sizey,this.resize_max_size_x=Math.min(this.resize_wgd.max_size_x||this.options.resize.max_size[0],this.options.max_cols-this.resize_initial_col+1),this.resize_max_size_y=this.resize_wgd.max_size_y||this.options.resize.max_size[1],this.resize_min_size_x=this.resize_wgd.min_size_x||this.options.resize.min_size[0]||1,this.resize_min_size_y=this.resize_wgd.min_size_y||this.options.resize.min_size[1]||1,this.resize_initial_last_col=this.get_highest_occupied_cell().col,this.set_dom_grid_width(this.cols),this.resize_dir={right:e.$player.is("."+this.resize_handle_class+"-x"),bottom:e.$player.is("."+this.resize_handle_class+"-y")},this.$resized_widget.css({"min-width":this.options.widget_base_dimensions[0],"min-height":this.options.widget_base_dimensions[1]});var s=this.$resized_widget.get(0).tagName;this.$resize_preview_holder=t("<"+s+" />",{"class":"preview-holder resize-preview-holder","data-row":this.$resized_widget.attr("data-row"),"data-col":this.$resized_widget.attr("data-col"),css:{width:this.resize_initial_width,height:this.resize_initial_height}}).appendTo(this.$el),this.$resized_widget.addClass("resizing"),this.options.resize.start&&this.options.resize.start.call(this,i,e,this.$resized_widget),this.$el.trigger("gridster:resizestart")},r.on_stop_resize=function(i,e){this.$resized_widget.removeClass("resizing").css({width:"",height:""}),delay(t.proxy(function(){this.$resize_preview_holder.remove().css({"min-width":"","min-height":""}),this.options.resize.stop&&this.options.resize.stop.call(this,i,e,this.$resized_widget),this.$el.trigger("gridster:resizestop")},this),300),this.set_dom_grid_width(),this.options.autogrow_cols&&this.drag_api.set_limits(this.cols*this.min_widget_width)},r.on_resize=function(t,i){var e,s=i.pointer.diff_left,r=i.pointer.diff_top,o=this.options.widget_base_dimensions[0],a=this.options.widget_base_dimensions[1],n=this.options.widget_margins[0],h=this.options.widget_margins[1],_=this.resize_max_size_x,d=this.resize_min_size_x,l=this.resize_max_size_y,c=this.resize_min_size_y,p=this.options.autogrow_cols,g=1/0,u=1/0,f=Math.ceil(s/(o+2*n)-.2),w=Math.ceil(r/(a+2*h)-.2),m=Math.max(1,this.resize_initial_sizex+f),y=Math.max(1,this.resize_initial_sizey+w),v=this.container_width/this.min_widget_width-this.resize_initial_col+1,z=v*this.min_widget_width-2*n;if(m=Math.max(Math.min(m,_),d),m=Math.min(v,m),e=_*o+2*(m-1)*n,g=Math.min(e,z),min_width=d*o+2*(m-1)*n,y=Math.max(Math.min(y,l),c),u=l*a+2*(y-1)*h,min_height=c*a+2*(y-1)*h,this.resize_dir.right?y=this.resize_initial_sizey:this.resize_dir.bottom&&(m=this.resize_initial_sizex),p){var x=this.resize_initial_col+m-1;p&&x>=this.resize_initial_last_col&&(this.set_dom_grid_width(Math.max(x+1,this.cols)),x>this.cols&&this.add_faux_cols(x-this.cols))}var $={};!this.resize_dir.bottom&&($.width=Math.max(Math.min(this.resize_initial_width+s,g),min_width)),!this.resize_dir.right&&($.height=Math.max(Math.min(this.resize_initial_height+r,u),min_height)),this.$resized_widget.css($),(m!==this.resize_last_sizex||y!==this.resize_last_sizey)&&(this.resize_widget(this.$resized_widget,m,y),this.set_dom_grid_width(this.cols),this.$resize_preview_holder.css({width:"",height:""}).attr({"data-row":this.$resized_widget.attr("data-row"),"data-sizex":m,"data-sizey":y})),this.options.resize.resize&&this.options.resize.resize.call(this,t,i,this.$resized_widget),this.$el.trigger("gridster:resize"),this.resize_last_sizex=m,this.resize_last_sizey=y},r.on_overlapped_column_change=function(i,e){if(!this.colliders_data.length)return this;var s,r=this.get_targeted_columns(this.colliders_data[0].el.data.col),o=this.last_cols.length,a=r.length;for(s=0;a>s;s++)-1===t.inArray(r[s],this.last_cols)&&(i||t.noop).call(this,r[s]);for(s=0;o>s;s++)-1===t.inArray(this.last_cols[s],r)&&(e||t.noop).call(this,this.last_cols[s]);return this.last_cols=r,this},r.on_overlapped_row_change=function(i,e){if(!this.colliders_data.length)return this;var s,r=this.get_targeted_rows(this.colliders_data[0].el.data.row),o=this.last_rows.length,a=r.length;for(s=0;a>s;s++)-1===t.inArray(r[s],this.last_rows)&&(i||t.noop).call(this,r[s]);for(s=0;o>s;s++)-1===t.inArray(this.last_rows[s],r)&&(e||t.noop).call(this,this.last_rows[s]);this.last_rows=r},r.set_player=function(t,i,e){var s=this;e||this.empty_cells_player_occupies();var r=e?{col:t}:s.colliders_data[0].el.data,o=r.col,a=i||r.row;this.player_grid_data={col:o,row:a,size_y:this.player_grid_data.size_y,size_x:this.player_grid_data.size_x},this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data);var n=this.get_widgets_overlapped(this.player_grid_data),h=this.widgets_constraints(n);if(this.manage_movements(h.can_go_up,o,a),this.manage_movements(h.can_not_go_up,o,a),!n.length){var _=this.can_go_player_up(this.player_grid_data);_!==!1&&(a=_),this.set_placeholder(o,a)}return{col:o,row:a}},r.widgets_constraints=function(i){var s,r=t([]),o=[],a=[];return i.each(t.proxy(function(i,e){var s=t(e),n=s.coords().grid;this.can_go_widget_up(n)?(r=r.add(s),o.push(n)):a.push(n)},this)),s=i.not(r),{can_go_up:e.sort_by_row_asc(o),can_not_go_up:e.sort_by_row_desc(a)}},r.manage_movements=function(i,e,s){return t.each(i,t.proxy(function(t,i){var r=i,o=r.el,a=this.can_go_widget_up(r);if(a)this.move_widget_to(o,a),this.set_placeholder(e,a+r.size_y);else{var n=this.can_go_player_up(this.player_grid_data);if(!n){var h=s+this.player_grid_data.size_y-r.row;this.move_widget_down(o,h),this.set_placeholder(e,s)}}},this)),this},r.is_player=function(t,i){if(i&&!this.gridmap[t])return!1;var e=i?this.gridmap[t][i]:t;return e&&(e.is(this.$player)||e.is(this.$helper))},r.is_player_in=function(i,e){var s=this.cells_occupied_by_player||{};return t.inArray(i,s.cols)>=0&&t.inArray(e,s.rows)>=0},r.is_placeholder_in=function(i,e){var s=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(i)&&t.inArray(e,s.rows)>=0},r.is_placeholder_in_col=function(i){var e=this.cells_occupied_by_placeholder||[];return t.inArray(i,e.cols)>=0},r.is_empty=function(t,i){return this.gridmap[t]!==void 0?this.gridmap[t][i]!==void 0&&this.gridmap[t][i]===!1?!0:!1:!0},r.is_occupied=function(t,i){return this.gridmap[t]?this.gridmap[t][i]?!0:!1:!1},r.is_widget=function(t,i){var e=this.gridmap[t];return e?(e=e[i],e?e:!1):!1},r.is_widget_under_player=function(t,i){return this.is_widget(t,i)?this.is_player_in(t,i):!1},r.get_widgets_under_player=function(i){i||(i=this.cells_occupied_by_player||{cols:[],rows:[]});var e=t([]);return t.each(i.cols,t.proxy(function(s,r){t.each(i.rows,t.proxy(function(t,i){this.is_widget(r,i)&&(e=e.add(this.gridmap[r][i]))},this))},this)),e},r.set_placeholder=function(i,e){var s=t.extend({},this.placeholder_grid_data),r=this.widgets_below({col:s.col,row:s.row,size_y:s.size_y,size_x:s.size_x}),o=i+s.size_x-1;o>this.cols&&(i-=o-i);var a=e>this.placeholder_grid_data.row,n=this.placeholder_grid_data.col!==i;this.placeholder_grid_data.col=i,this.placeholder_grid_data.row=e,this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.$preview_holder.attr({"data-row":e,"data-col":i}),(a||n)&&r.each(t.proxy(function(e,r){this.move_widget_up(t(r),this.placeholder_grid_data.col-i+s.size_y)},this));var h=this.get_widgets_under_player(this.cells_occupied_by_placeholder);h.length&&h.each(t.proxy(function(i,r){var o=t(r);this.move_widget_down(o,e+s.size_y-o.data("coords").grid.row)},this))},r.can_go_player_up=function(t){var i=t.row+t.size_y-1,e=!0,s=[],r=1e4,o=this.get_widgets_under_player();return this.for_each_column_occupied(t,function(t){var a=this.gridmap[t],n=i+1;for(s[t]=[];--n>0&&(this.is_empty(t,n)||this.is_player(t,n)||this.is_widget(t,n)&&a[n].is(o));)s[t].push(n),r=r>n?n:r;return 0===s[t].length?(e=!1,!0):(s[t].sort(function(t,i){return t-i -}),void 0)}),e?this.get_valid_rows(t,s,r):!1},r.can_go_widget_up=function(t){var i=t.row+t.size_y-1,e=!0,s=[],r=1e4;return this.for_each_column_occupied(t,function(o){var a=this.gridmap[o];s[o]=[];for(var n=i+1;--n>0&&(!this.is_widget(o,n)||this.is_player_in(o,n)||a[n].is(t.el));)this.is_player(o,n)||this.is_placeholder_in(o,n)||this.is_player_in(o,n)||s[o].push(n),r>n&&(r=n);return 0===s[o].length?(e=!1,!0):(s[o].sort(function(t,i){return t-i}),void 0)}),e?this.get_valid_rows(t,s,r):!1},r.get_valid_rows=function(i,e,s){for(var r=i.row,o=i.row+i.size_y-1,a=i.size_y,n=s-1,h=[];o>=++n;){var _=!0;if(t.each(e,function(i,e){t.isArray(e)&&-1===t.inArray(n,e)&&(_=!1)}),_===!0&&(h.push(n),h.length===a))break}var d=!1;return 1===a?h[0]!==r&&(d=h[0]||!1):h[0]!==r&&(d=this.get_consecutive_numbers_index(h,a)),d},r.get_consecutive_numbers_index=function(t,i){for(var e=t.length,s=[],r=!0,o=-1,a=0;e>a;a++){if(r||t[a]===o+1){if(s.push(a),s.length===i)break;r=!1}else s=[],r=!0;o=t[a]}return s.length>=i?t[s[0]]:!1},r.get_widgets_overlapped=function(){var i=t([]),e=[],s=this.cells_occupied_by_player.rows.slice(0);return s.reverse(),t.each(this.cells_occupied_by_player.cols,t.proxy(function(r,o){t.each(s,t.proxy(function(s,r){if(!this.gridmap[o])return!0;var a=this.gridmap[o][r];this.is_occupied(o,r)&&!this.is_player(a)&&-1===t.inArray(a,e)&&(i=i.add(a),e.push(a))},this))},this)),i},r.on_start_overlapping_column=function(t){this.set_player(t,!1)},r.on_start_overlapping_row=function(t){this.set_player(!1,t)},r.on_stop_overlapping_column=function(t){this.set_player(t,!1);var i=this;this.for_each_widget_below(t,this.cells_occupied_by_player.rows[0],function(){i.move_widget_up(this,i.player_grid_data.size_y)})},r.on_stop_overlapping_row=function(t){this.set_player(!1,t);for(var i=this,e=this.cells_occupied_by_player.cols,s=0,r=e.length;r>s;s++)this.for_each_widget_below(e[s],t,function(){i.move_widget_up(this,i.player_grid_data.size_y)})},r.move_widget_to=function(i,e){var s=this,r=i.coords().grid;e-r.row;var o=this.widgets_below(i),a=this.can_move_to(r,r.col,e,i);return a===!1?!1:(this.remove_from_gridmap(r),r.row=e,this.add_to_gridmap(r),i.attr("data-row",e),this.$changed=this.$changed.add(i),o.each(function(i,e){var r=t(e),o=r.coords().grid,a=s.can_go_widget_up(o);a&&a!==o.row&&s.move_widget_to(r,a)}),this)},r.move_widget_up=function(i,e){var s=i.coords().grid,r=s.row,o=[];return e||(e=1),this.can_go_up(i)?(this.for_each_column_occupied(s,function(s){if(-1===t.inArray(i,o)){var a=i.coords().grid,n=r-e;if(n=this.can_go_up_to_row(a,s,n),!n)return!0;var h=this.widgets_below(i);this.remove_from_gridmap(a),a.row=n,this.add_to_gridmap(a),i.attr("data-row",a.row),this.$changed=this.$changed.add(i),o.push(i),h.each(t.proxy(function(i,s){this.move_widget_up(t(s),e)},this))}}),void 0):!1},r.move_widget_down=function(i,e){var s,r,o,a;if(0>=e)return!1;if(s=i.coords().grid,r=s.row,o=[],a=e,!i)return!1;if(-1===t.inArray(i,o)){var n=i.coords().grid,h=r+e,_=this.widgets_below(i);this.remove_from_gridmap(n),_.each(t.proxy(function(i,e){var s=t(e),r=s.coords().grid,o=this.displacement_diff(r,n,a);o>0&&this.move_widget_down(s,o)},this)),n.row=h,this.update_widget_position(n,i),i.attr("data-row",n.row),this.$changed=this.$changed.add(i),o.push(i)}},r.can_go_up_to_row=function(i,e,s){var r,o=this.gridmap,a=!0,n=[],h=i.row;if(this.for_each_column_occupied(i,function(t){for(o[t],n[t]=[],r=h;r--&&this.is_empty(t,r)&&!this.is_placeholder_in(t,r);)n[t].push(r);return n[t].length?void 0:(a=!1,!0)}),!a)return!1;for(r=s,r=1;h>r;r++){for(var _=!0,d=0,l=n.length;l>d;d++)n[d]&&-1===t.inArray(r,n[d])&&(_=!1);if(_===!0){a=r;break}}return a},r.displacement_diff=function(t,i,e){var s=t.row,r=[],o=i.row+i.size_y;this.for_each_column_occupied(t,function(t){for(var i=0,e=o;s>e;e++)this.is_empty(t,e)&&(i+=1);r.push(i)});var a=Math.max.apply(Math,r);return e-=a,e>0?e:0},r.widgets_below=function(i){var s=t.isPlainObject(i)?i:i.coords().grid,r=this;this.gridmap;var o=s.row+s.size_y-1,a=t([]);return this.for_each_column_occupied(s,function(i){r.for_each_widget_below(i,o,function(){return r.is_player(this)||-1!==t.inArray(this,a)?void 0:(a=a.add(this),!0)})}),e.sort_by_row_asc(a)},r.set_cells_player_occupies=function(t,i){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=t,this.placeholder_grid_data.row=i,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},r.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},r.can_go_up=function(t){var i=t.coords().grid,e=i.row,s=e-1;this.gridmap;var r=!0;return 1===e?!1:(this.for_each_column_occupied(i,function(t){return this.is_widget(t,s),this.is_occupied(t,s)||this.is_player(t,s)||this.is_placeholder_in(t,s)||this.is_player_in(t,s)?(r=!1,!0):void 0}),r)},r.can_move_to=function(t,i,e,s){this.gridmap;var r=t.el,o={size_y:t.size_y,size_x:t.size_x,col:i,row:e},a=!0,n=i+t.size_x-1;return n>this.cols?!1:s&&e+t.size_y-1>s?!1:(this.for_each_cell_occupied(o,function(i,e){var s=this.is_widget(i,e);!s||t.el&&!s.is(r)||(a=!1)}),a)},r.get_targeted_columns=function(t){for(var i=(t||this.player_grid_data.col)+(this.player_grid_data.size_x-1),e=[],s=t;i>=s;s++)e.push(s);return e},r.get_targeted_rows=function(t){for(var i=(t||this.player_grid_data.row)+(this.player_grid_data.size_y-1),e=[],s=t;i>=s;s++)e.push(s);return e},r.get_cells_occupied=function(i){var e,s={cols:[],rows:[]};for(arguments[1]instanceof t&&(i=arguments[1].coords().grid),e=0;i.size_x>e;e++){var r=i.col+e;s.cols.push(r)}for(e=0;i.size_y>e;e++){var o=i.row+e;s.rows.push(o)}return s},r.for_each_cell_occupied=function(t,i){return this.for_each_column_occupied(t,function(e){this.for_each_row_occupied(t,function(t){i.call(this,e,t)})}),this},r.for_each_column_occupied=function(t,i){for(var e=0;t.size_x>e;e++){var s=t.col+e;i.call(this,s,t)}},r.for_each_row_occupied=function(t,i){for(var e=0;t.size_y>e;e++){var s=t.row+e;i.call(this,s,t)}},r._traversing_widgets=function(i,e,s,r,o){var a=this.gridmap;if(a[s]){var n,h,_=i+"/"+e;if(arguments[2]instanceof t){var d=arguments[2].coords().grid;s=d.col,r=d.row,o=arguments[3]}var l=[],c=r,p={"for_each/above":function(){for(;c--&&!(c>0&&this.is_widget(s,c)&&-1===t.inArray(a[s][c],l)&&(n=o.call(a[s][c],s,c),l.push(a[s][c]),n)););},"for_each/below":function(){for(c=r+1,h=a[s].length;h>c&&(!this.is_widget(s,c)||-1!==t.inArray(a[s][c],l)||(n=o.call(a[s][c],s,c),l.push(a[s][c]),!n));c++);}};p[_]&&p[_].call(this)}},r.for_each_widget_above=function(t,i,e){return this._traversing_widgets("for_each","above",t,i,e),this},r.for_each_widget_below=function(t,i,e){return this._traversing_widgets("for_each","below",t,i,e),this},r.get_highest_occupied_cell=function(){for(var t,i=this.gridmap,e=i[1].length,s=[],r=[],o=i.length-1;o>=1;o--)for(t=e-1;t>=1;t--)if(this.is_widget(o,t)){s.push(t),r.push(o);break}return{col:Math.max.apply(Math,r),row:Math.max.apply(Math,s)}},r.get_widgets_from=function(i,e){this.gridmap;var s=t();return i&&(s=s.add(this.$widgets.filter(function(){var e=t(this).attr("data-col");return e===i||e>i}))),e&&(s=s.add(this.$widgets.filter(function(){var i=t(this).attr("data-row");return i===e||i>e}))),s},r.set_dom_grid_height=function(t){if(t===void 0){var i=this.get_highest_occupied_cell().row;t=i*this.min_widget_height}return this.container_height=t,this.$el.css("height",this.container_height),this},r.set_dom_grid_width=function(t){t===void 0&&(t=this.get_highest_occupied_cell().col);var i=this.options.autogrow_cols?this.options.max_cols:this.cols;return t=Math.min(i,Math.max(t,this.options.min_cols)),this.container_width=t*this.min_widget_width,this.$el.css("width",this.container_width),this},r.generate_stylesheet=function(i){var s,r="",o=this.options.max_size_x||this.cols;i||(i={}),i.cols||(i.cols=this.cols),i.rows||(i.rows=this.rows),i.namespace||(i.namespace=this.options.namespace),i.widget_base_dimensions||(i.widget_base_dimensions=this.options.widget_base_dimensions),i.widget_margins||(i.widget_margins=this.options.widget_margins),i.min_widget_width=2*i.widget_margins[0]+i.widget_base_dimensions[0],i.min_widget_height=2*i.widget_margins[1]+i.widget_base_dimensions[1];var a=t.param(i);if(t.inArray(a,e.generated_stylesheets)>=0)return!1;for(this.generated_stylesheets.push(a),e.generated_stylesheets.push(a),s=i.cols;s>=0;s--)r+=i.namespace+' [data-col="'+(s+1)+'"] { left:'+(s*i.widget_base_dimensions[0]+s*i.widget_margins[0]+(s+1)*i.widget_margins[0])+"px; }\n";for(s=i.rows;s>=0;s--)r+=i.namespace+' [data-row="'+(s+1)+'"] { top:'+(s*i.widget_base_dimensions[1]+s*i.widget_margins[1]+(s+1)*i.widget_margins[1])+"px; }\n";for(var n=1;i.rows>=n;n++)r+=i.namespace+' [data-sizey="'+n+'"] { height:'+(n*i.widget_base_dimensions[1]+(n-1)*2*i.widget_margins[1])+"px; }\n";for(var h=1;o>=h;h++)r+=i.namespace+' [data-sizex="'+h+'"] { width:'+(h*i.widget_base_dimensions[0]+(h-1)*2*i.widget_margins[0])+"px; }\n";return this.remove_style_tags(),this.add_style_tag(r)},r.add_style_tag=function(t){var i=document,e=i.createElement("style");return i.getElementsByTagName("head")[0].appendChild(e),e.setAttribute("type","text/css"),e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t)),this.$style_tags=this.$style_tags.add(e),this},r.remove_style_tags=function(){var i=e.generated_stylesheets,s=this.generated_stylesheets;this.$style_tags.remove(),e.generated_stylesheets=t.map(i,function(i){return-1===t.inArray(i,s)?i:void 0})},r.generate_faux_grid=function(t,i){this.faux_grid=[],this.gridmap=[];var e,s;for(e=i;e>0;e--)for(this.gridmap[e]=[],s=t;s>0;s--)this.add_faux_cell(s,e);return this},r.add_faux_cell=function(i,e){var s=t({left:this.baseX+(e-1)*this.min_widget_width,top:this.baseY+(i-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:e,row:i,original_col:e,original_row:i}).coords();return t.isArray(this.gridmap[e])||(this.gridmap[e]=[]),this.gridmap[e][i]=!1,this.faux_grid.push(s),this},r.add_faux_rows=function(t){for(var i=this.rows,e=i+(t||1),s=e;s>i;s--)for(var r=this.cols;r>=1;r--)this.add_faux_cell(s,r);return this.rows=e,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},r.add_faux_cols=function(t){var i=this.cols,e=i+(t||1);e=Math.min(e,this.options.max_cols);for(var s=i+1;e>=s;s++)for(var r=this.rows;r>=1;r--)this.add_faux_cell(r,s);return this.cols=e,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},r.recalculate_faux_grid=function(){var i=this.$wrapper.width();return this.baseX=(t(window).width()-i)/2,this.baseY=this.$wrapper.offset().top,t.each(this.faux_grid,t.proxy(function(t,i){this.faux_grid[t]=i.update({left:this.baseX+(i.data.col-1)*this.min_widget_width,top:this.baseY+(i.data.row-1)*this.min_widget_height})},this)),this},r.get_widgets_from_DOM=function(){var i=this.$widgets.map(t.proxy(function(i,e){var s=t(e);return this.dom_to_coords(s)},this));i=e.sort_by_row_and_col_asc(i);var s=t(i).map(t.proxy(function(t,i){return this.register_widget(i)||null},this));return s.length&&this.$el.trigger("gridster:positionschanged"),this},r.generate_grid_and_stylesheet=function(){var i=this.$wrapper.width(),e=this.options.max_cols,s=Math.floor(i/this.min_widget_width)+this.options.extra_cols,r=this.$widgets.map(function(){return t(this).attr("data-col")}).get();r.length||(r=[0]);var o=Math.max.apply(Math,r);this.cols=Math.max(o,s,this.options.min_cols),1/0!==e&&e>=o&&this.cols>e&&(this.cols=e);var a=this.options.extra_rows;return this.$widgets.each(function(i,e){a+=+t(e).attr("data-sizey")}),this.rows=Math.max(a,this.options.min_rows),this.baseX=(t(window).width()-i)/2,this.baseY=this.$wrapper.offset().top,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this.generate_faux_grid(this.rows,this.cols)},r.destroy=function(i){return this.$el.removeData("gridster"),t(window).unbind(".gridster"),this.drag_api&&this.drag_api.destroy(),this.remove_style_tags(),i&&this.$el.remove(),this},t.fn.gridster=function(i){return this.each(function(){t(this).data("gridster")||t(this).data("gridster",new e(this,i))})},e}); \ No newline at end of file +/*! gridster.js - v0.6.10 - 2015-08-05 - * https://dsmorse.github.io/gridster.js/ - Copyright (c) 2015 ducksboard; Licensed MIT */ !function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define("gridster-coords",["jquery"],b):a.GridsterCoords=b(a.$||a.jQuery)}(this,function(a){"use strict";function b(b){return b[0]&&a.isPlainObject(b[0])?this.data=b[0]:this.el=b,this.isCoords=!0,this.coords={},this.init(),this}var c=b.prototype;return c.init=function(){this.set(),this.original_coords=this.get()},c.set=function(a,b){var c=this.el;if(c&&!a&&(this.data=c.offset(),this.data.width=c[0].scrollWidth,this.data.height=c[0].scrollHeight),c&&a&&!b){var d=c.offset();this.data.top=d.top,this.data.left=d.left}var e=this.data;return void 0===e.left&&(e.left=e.x1),void 0===e.top&&(e.top=e.y1),this.coords.x1=e.left,this.coords.y1=e.top,this.coords.x2=e.left+e.width,this.coords.y2=e.top+e.height,this.coords.cx=e.left+e.width/2,this.coords.cy=e.top+e.height/2,this.coords.width=e.width,this.coords.height=e.height,this.coords.el=c||!1,this},c.update=function(b){if(!b&&!this.el)return this;if(b){var c=a.extend({},this.data,b);return this.data=c,this.set(!0,!0)}return this.set(!0),this},c.get=function(){return this.coords},c.destroy=function(){this.el.removeData("coords"),delete this.el},a.fn.coords=function(){if(this.data("coords"))return this.data("coords");var a=new b(this);return this.data("coords",a),a},b}),function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define("gridster-collision",["jquery","gridster-coords"],b):a.GridsterCollision=b(a.$||a.jQuery,a.GridsterCoords)}(this,function(a,b){"use strict";function c(b,c,e){this.options=a.extend(d,e),this.$element=b,this.last_colliders=[],this.last_colliders_coords=[],this.set_colliders(c),this.init()}var d={colliders_context:document.body,overlapping_region:"C"};c.defaults=d;var e=c.prototype;return e.init=function(){this.find_collisions()},e.overlaps=function(a,b){var c=!1,d=!1;return(b.x1>=a.x1&&b.x1<=a.x2||b.x2>=a.x1&&b.x2<=a.x2||a.x1>=b.x1&&a.x2<=b.x2)&&(c=!0),(b.y1>=a.y1&&b.y1<=a.y2||b.y2>=a.y1&&b.y2<=a.y2||a.y1>=b.y1&&a.y2<=b.y2)&&(d=!0),c&&d},e.detect_overlapping_region=function(a,b){var c="",d="";return a.y1>b.cy&&a.y1b.y1&&a.y2b.cx&&a.x1b.x1&&a.x2f;f++)-1===a.inArray(e[f],b)&&c.call(this,e[f]);for(var h=0,i=b.length;i>h;h++)-1===a.inArray(b[h],e)&&d.call(this,b[h])},e.find_collisions=function(b){for(var c=this,d=this.options.overlapping_region,e=[],f=[],g=this.colliders||this.$colliders,h=g.length,i=c.$element.coords().update(b||!1).get();h--;){var j=c.$colliders?a(g[h]):g[h],k=j.isCoords?j:j.coords(),l=k.get(),m=c.overlaps(i,l);if(m){var n=c.detect_overlapping_region(i,l);if(n===d||"all"===d){var o=c.calculate_overlapped_area_coords(i,l),p=c.calculate_overlapped_area(o);if(0!==p){var q={area:p,area_coords:o,region:n,coords:l,player_coords:i,el:j};c.options.on_overlap&&c.options.on_overlap.call(this,q),e.push(k),f.push(q)}}}}return(c.options.on_overlap_stop||c.options.on_overlap_start)&&this.manage_colliders_start_stop(e,c.options.on_overlap_start,c.options.on_overlap_stop),this.last_colliders_coords=e,f},e.get_closest_colliders=function(a){var b=this.find_collisions(a);return b.sort(function(a,b){return"C"===a.region&&"C"===b.region?a.coords.y1this.player_max_left?e=this.player_max_left:e=q&&(l=n+h,t>l&&(this.$scroll_container[i](l),this["scroll_offset_"+a]+=h)),r>=s&&(l=n-h,l>0&&(this.$scroll_container[i](l),this["scroll_offset_"+a]-=h)),this},j.manage_scroll=function(a){this.scroll_in("x",a),this.scroll_in("y",a)},j.calculate_dimensions=function(){this.scroller_height=this.$scroll_container.height(),this.scroller_width=this.$scroll_container.width()},j.drag_handler=function(b){if(!this.disabled&&(1===b.which||f)&&!this.ignore_drag(b)){var c=this,d=!0;return this.$player=a(b.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(b),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.$document.on(this.pointer_events.move,function(a){var b=c.get_mouse_pos(a),e=Math.abs(b.left-c.mouse_init_pos.left),f=Math.abs(b.top-c.mouse_init_pos.top);return e>c.options.distance||f>c.options.distance?d?(d=!1,c.on_dragstart.call(c,a),!1):(c.is_dragging===!0&&c.on_dragmove.call(c,a),!1):!1}),f?void 0:!1}},j.on_dragstart=function(a){if(a.preventDefault(),this.is_dragging)return this;this.drag_start=this.is_dragging=!0;var b=this.$container.offset();return this.baseX=Math.round(b.left),this.baseY=Math.round(b.top),"clone"===this.options.helper?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.scroll_container_offset_y=this.$scroll_container.scrollTop(),this.scroll_container_offset_x=this.$scroll_container.scrollLeft(),this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.set_limits(this.options.container_width),this.options.start&&this.options.start.call(this.$player,a,this.get_drag_data(a)),!1},j.on_dragmove=function(a){var b=this.get_drag_data(a);this.options.autoscroll&&this.manage_scroll(b),this.options.move_element&&(this.helper?this.$helper:this.$player).css({position:"absolute",left:b.position.left,top:b.position.top});var c=this.last_position||b.position;return b.prev_position=c,this.options.drag&&this.options.drag.call(this.$player,a,b),this.last_position=b.position,!1},j.on_dragstop=function(a){var b=this.get_drag_data(a);return this.drag_start=!1,this.options.stop&&this.options.stop.call(this.$player,a,b),this.helper&&this.options.remove_helper&&this.$helper.remove(),!1},j.on_select_start=function(a){return this.disabled||this.ignore_drag(a)?void 0:!1},j.enable=function(){this.disabled=!1},j.disable=function(){this.disabled=!0},j.destroy=function(){this.disable(),this.$container.off(this.ns),this.$document.off(this.ns),d.off(this.ns),a.removeData(this.$container,"drag")},j.ignore_drag=function(b){return this.options.handle?!a(b.target).is(this.options.handle):a.isFunction(this.options.ignore_dragging)?this.options.ignore_dragging(b):this.options.resize?!a(b.target).is(this.options.items):a(b.target).is(this.options.ignore_dragging.join(", "))},a.fn.gridDraggable=function(a){return new b(this,a)},a.fn.dragg=function(c){return this.each(function(){a.data(this,"drag")||a.data(this,"drag",new b(this,c))})},b}),function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery"),require("./jquery.draggable.js"),require("./jquery.collision.js"),require("./jquery.coords.js"),require("./utils.js")):"function"==typeof define&&define.amd?define(["jquery","gridster-draggable","gridster-collision"],b):a.Gridster=b(a.$||a.jQuery,a.GridsterDraggable,a.GridsterCollision)}(this,function(a,b,c){"use strict";function d(b,c){this.options=a.extend(!0,{},g,c),this.options.draggable=this.options.draggable||{},this.options.draggable=a.extend(!0,{},this.options.draggable,{scroll_container:this.options.scroll_container}),this.$el=a(b),this.$scroll_container=this.options.scroll_container===window?a(window):this.$el.closest(this.options.scroll_container),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs-w"),this.$changed=a([]),this.w_queue={},this.is_responsive()?this.min_widget_width=this.get_responsive_col_width():this.min_widget_width=this.options.widget_base_dimensions[0],this.min_widget_height=this.options.widget_base_dimensions[1],this.min_col_count=this.options.min_cols,this.prev_col_count=this.min_col_count,this.generated_stylesheets=[],this.$style_tags=a([]),this.options.auto_init&&this.init()}function e(a){for(var b=["col","row","size_x","size_y"],c={},d=0,e=b.length;e>d;d++){var f=b[d];if(!(f in a))throw new Error("Not exists property `"+f+"`");var g=a[f];if(!g||isNaN(g))throw new Error("Invalid value of `"+f+"` property");c[f]=+g}return c}var f=a(window),g={namespace:"",widget_selector:"li",static_class:"static",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,max_cols:1/0,min_rows:1,max_rows:15,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,auto_init:!0,center_widgets:!1,responsive_breakpoint:!1,scroll_container:window,shift_larger_widgets_down:!0,shift_widgets_up:!0,show_element:function(a,b){b?a.fadeIn(b):a.fadeIn()},hide_element:function(a,b){b?a.fadeOut(b):a.fadeOut()},serialize_params:function(a,b){return{col:b.col,row:b.row,size_x:b.size_x,size_y:b.size_y}},collision:{wait_for_mouseup:!1},draggable:{items:".gs-w:not(.static)",distance:4,ignore_dragging:b.defaults.ignore_dragging.slice(0)},resize:{enabled:!1,axes:["both"],handle_append_to:"",handle_class:"gs-resize-handle",max_size:[1/0,1/0],min_size:[1,1]}};d.defaults=g,d.generated_stylesheets=[],d.sort_by_row_asc=function(b){return b=b.sort(function(b,c){return b.row||(b=a(b).coords().grid,c=a(c).coords().grid),b=e(b),c=e(c),b.row>c.row?1:-1})},d.sort_by_row_and_col_asc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.row>b.row||a.row===b.row&&a.col>b.col?1:-1})},d.sort_by_col_asc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.col>b.col?1:-1})},d.sort_by_row_desc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.row+a.size_yi&&this.add_faux_rows(Math.max(d-i,0));var k={col:j,row:g.row,size_x:c,size_y:d};return this.mutate_widget_in_gridmap(b,g,k),this.set_dom_grid_height(),this.set_dom_grid_width(),f&&f.call(this,k.size_x,k.size_y),b},h.collapse_widget=function(a,b){var c=a.coords().grid,d=parseInt(a.attr("pre_expand_sizex")),e=parseInt(a.attr("pre_expand_sizey")),f=parseInt(a.attr("pre_expand_col")),g={col:f,row:c.row,size_x:d,size_y:e};return this.mutate_widget_in_gridmap(a,c,g),this.set_dom_grid_height(),this.set_dom_grid_width(),b&&b.call(this,g.size_x,g.size_y),a},h.fit_to_content=function(a,b,c,d){var e=a.coords().grid,f=this.$wrapper.width(),g=this.$wrapper.height(),h=this.options.widget_base_dimensions[0]+2*this.options.widget_margins[0],i=this.options.widget_base_dimensions[1]+2*this.options.widget_margins[1],j=Math.ceil((f+2*this.options.widget_margins[0])/h),k=Math.ceil((g+2*this.options.widget_margins[1])/i),l={col:e.col,row:e.row,size_x:Math.min(b,j),size_y:Math.min(c,k)};return this.mutate_widget_in_gridmap(a,e,l),this.set_dom_grid_height(),this.set_dom_grid_width(),d&&d.call(this,l.size_x,l.size_y),a},h.center_widgets=debounce(function(){var b,c=this.$wrapper.width();b=this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0]+2*this.options.widget_margins[0];var d=2*Math.floor(Math.max(Math.floor(c/b),this.min_col_count)/2);this.options.min_cols=d,this.options.max_cols=d,this.options.extra_cols=0,this.set_dom_grid_width(d),this.cols=d;var e=(d-this.prev_col_count)/2;return 0>e?(this.get_min_col()>-1*e?this.shift_cols(e):this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.resize_widget_dimensions(this.options)},this),0)):e>0?(this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.shift_cols(e)},this),0)):(this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.resize_widget_dimensions(this.options)},this),0)),this.prev_col_count=d,this},200),h.get_min_col=function(){return Math.min.apply(Math,this.$widgets.map(a.proxy(function(b,c){return this.get_cells_occupied(a(c).coords().grid).cols},this)).get())},h.shift_cols=function(b){var c=this.$widgets.map(a.proxy(function(b,c){var d=a(c);return this.dom_to_coords(d)},this));c=d.sort_by_row_and_col_asc(c),c.each(a.proxy(function(c,d){var e=a(d.el),f=e.coords().grid,g=parseInt(e.attr("data-col")),h={col:Math.max(Math.round(g+b),1),row:f.row,size_x:f.size_x,size_y:f.size_y};setTimeout(a.proxy(function(){this.mutate_widget_in_gridmap(e,f,h)},this),0)},this))},h.resize_widget_dimensions=function(b){return b.widget_margins&&(this.options.widget_margins=b.widget_margins),b.widget_base_dimensions&&(this.options.widget_base_dimensions=b.widget_base_dimensions),this.min_widget_width=2*this.options.widget_margins[0]+this.options.widget_base_dimensions[0],this.min_widget_height=2*this.options.widget_margins[1]+this.options.widget_base_dimensions[1],this.$widgets.each(a.proxy(function(b,c){var d=a(c);this.resize_widget(d)},this)),this.generate_grid_and_stylesheet(),this.get_widgets_from_DOM(),this.set_dom_grid_height(),this},h.mutate_widget_in_gridmap=function(b,c,d){var e=c.size_y,f=this.get_cells_occupied(c),g=this.get_cells_occupied(d),h=[];a.each(f.cols,function(b,c){-1===a.inArray(c,g.cols)&&h.push(c)});var i=[];a.each(g.cols,function(b,c){-1===a.inArray(c,f.cols)&&i.push(c)});var j=[];a.each(f.rows,function(b,c){-1===a.inArray(c,g.rows)&&j.push(c)});var k=[];if(a.each(g.rows,function(b,c){-1===a.inArray(c,f.rows)&&k.push(c)}),this.remove_from_gridmap(c),i.length){var l=[d.col,d.row,d.size_x,Math.min(e,d.size_y),b];this.empty_cells.apply(this,l)}if(k.length){var m=[d.col,d.row,d.size_x,d.size_y,b];this.empty_cells.apply(this,m)}if(c.col=d.col,c.row=d.row,c.size_x=d.size_x,c.size_y=d.size_y,this.add_to_gridmap(d,b),b.removeClass("player-revert"),this.update_widget_dimensions(b,d),h.length){var n=[h[0],d.row,h[h.length-1]-h[0]+1,Math.min(e,d.size_y),b];this.remove_empty_cells.apply(this,n)}if(j.length){var o=[d.col,d.row,d.size_x,d.size_y,b];this.remove_empty_cells.apply(this,o)}return this.move_widget_up(b),this},h.empty_cells=function(b,c,d,e,f){var g=this.widgets_below({col:b,row:c-e,size_x:d,size_y:e});return g.not(f).each(a.proxy(function(b,d){var f=a(d),g=f.coords().grid;if(g.row<=c+e-1){var h=c+e-g.row;this.move_widget_down(f,h)}},this)),this.set_dom_grid_height(),this},h.remove_empty_cells=function(b,c,d,e,f){var g=this.widgets_below({col:b,row:c,size_x:d,size_y:e});return g.not(f).each(a.proxy(function(b,c){this.move_widget_up(a(c),e)},this)),this.set_dom_grid_height(),this},h.next_position=function(a,b){a||(a=1),b||(b=1);for(var c,e=this.gridmap,f=e.length,g=[],h=1;f>h;h++){c=e[h].length;for(var i=1;c>=i;i++){var j=this.can_move_to({size_x:a,size_y:b},h,i);j&&g.push({col:h,row:i,size_y:b,size_x:a})}}return g.length?d.sort_by_row_and_col_asc(g)[0]:!1},h.remove_by_grid=function(a,b){var c=this.is_widget(a,b);c&&this.remove_widget(c)},h.remove_widget=function(b,c,d){var e=b instanceof a?b:a(b);if(0===e.length)return this;var f=e.coords().grid;if(void 0===f)return this;a.isFunction(c)&&(d=c,c=!1),this.cells_occupied_by_placeholder={},this.$widgets=this.$widgets.not(e);var g=this.widgets_below(e);return this.remove_from_gridmap(f),this.options.hide_element.call(this,e,a.proxy(function(){e.remove(),c||g.each(a.proxy(function(b,c){this.move_widget_up(a(c),f.size_y)},this)),this.set_dom_grid_height(),d&&d.call(this,b)},this)),this},h.remove_all_widgets=function(b){return this.$widgets.each(a.proxy(function(a,c){this.remove_widget(c,!0,b)},this)),this},h.serialize=function(b){b||(b=this.$widgets);var c=[];return b.each(a.proxy(function(b,d){var e=a(d);"undefined"!=typeof e.coords().grid&&c.push(this.options.serialize_params(e,e.coords().grid))},this)),c},h.serialize_changed=function(){return this.serialize(this.$changed)},h.dom_to_coords=function(a){return{col:parseInt(a.attr("data-col"),10),row:parseInt(a.attr("data-row"),10),size_x:parseInt(a.attr("data-sizex"),10)||1,size_y:parseInt(a.attr("data-sizey"),10)||1,max_size_x:parseInt(a.attr("data-max-sizex"),10)||!1,max_size_y:parseInt(a.attr("data-max-sizey"),10)||!1,min_size_x:parseInt(a.attr("data-min-sizex"),10)||!1,min_size_y:parseInt(a.attr("data-min-sizey"),10)||!1,el:a}},h.register_widget=function(b){var c=b instanceof a,d=c?this.dom_to_coords(b):b,e=!1;c||(b=d.el);var f=this.can_go_widget_up(d);return this.options.shift_widgets_up&&f&&(d.row=f,b.attr("data-row",f),this.$el.trigger("gridster:positionchanged",[d]),e=!0),this.options.avoid_overlapped_widgets&&!this.can_move_to({size_x:d.size_x,size_y:d.size_y},d.col,d.row)&&(a.extend(d,this.next_position(d.size_x,d.size_y)),b.attr({"data-col":d.col,"data-row":d.row,"data-sizex":d.size_x,"data-sizey":d.size_y}),e=!0),b.data("coords",b.coords()),b.data("coords").grid=d,this.add_to_gridmap(d,b),this.update_widget_dimensions(b,d),this.options.resize.enabled&&this.add_resize_handle(b),e},h.update_widget_position=function(a,b){return this.for_each_cell_occupied(a,function(a,c){return this.gridmap[a]?void(this.gridmap[a][c]=b):this}),this},h.update_widget_dimensions=function(a,b){var c=b.size_x*(this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0])+(b.size_x-1)*this.options.widget_margins[0],d=b.size_y*this.options.widget_base_dimensions[1]+(b.size_y-1)*this.options.widget_margins[1];return a.data("coords").update({width:c,height:d}),a.attr({"data-col":b.col,"data-row":b.row,"data-sizex":b.size_x,"data-sizey":b.size_y}),this},h.update_widgets_dimensions=function(){return a.each(this.$widgets,a.proxy(function(b,c){var d=a(c).coords().grid;"object"==typeof d&&this.update_widget_dimensions(a(c),d)},this)),this},h.remove_from_gridmap=function(a){return this.update_widget_position(a,!1)},h.add_to_gridmap=function(a,b){this.update_widget_position(a,b||a.el)},h.draggable=function(){var b=this,c=a.extend(!0,{},this.options.draggable,{offset_left:this.options.widget_margins[0],offset_top:this.options.widget_margins[1],container_width:this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0],limit:!0,start:function(c,d){b.$widgets.filter(".player-revert").removeClass("player-revert"),b.$player=a(this),b.$helper=a(d.$helper),b.helper=!b.$helper.is(b.$player),b.on_start_drag.call(b,c,d),b.$el.trigger("gridster:dragstart")},stop:function(a,c){b.on_stop_drag.call(b,a,c),b.$el.trigger("gridster:dragstop")},drag:throttle(function(a,c){b.on_drag.call(b,a,c),b.$el.trigger("gridster:drag")},60)});this.drag_api=this.$el.dragg(c).data("drag")},h.resizable=function(){return this.resize_api=this.$el.gridDraggable({items:"."+this.options.resize.handle_class,offset_left:this.options.widget_margins[0],container_width:this.container_width,move_element:!1,resize:!0,limit:this.options.max_cols!==1/0,scroll_container:this.options.scroll_container,start:a.proxy(this.on_start_resize,this),stop:a.proxy(function(b,c){delay(a.proxy(function(){this.on_stop_resize(b,c)},this),120)},this),drag:throttle(a.proxy(this.on_resize,this),60)}),this},h.setup_resize=function(){this.resize_handle_class=this.options.resize.handle_class;var b=this.options.resize.axes,c='';return this.resize_handle_tpl=a.map(b,function(a){return c.replace("{type}",a)}).join(""),a.isArray(this.options.draggable.ignore_dragging)&&this.options.draggable.ignore_dragging.push("."+this.resize_handle_class),this},h.on_start_drag=function(b,c){this.$helper.add(this.$player).add(this.$wrapper).addClass("dragging"),this.highest_col=this.get_highest_occupied_cell().col,this.$player.addClass("player"),this.player_grid_data=this.$player.coords().grid,this.placeholder_grid_data=a.extend({},this.player_grid_data),this.set_dom_grid_height(this.$el.height()+this.player_grid_data.size_y*this.min_widget_height),this.set_dom_grid_width(this.cols);var d=this.player_grid_data.size_x,e=this.cols-this.highest_col;this.options.max_cols===1/0&&d>=e&&this.add_faux_cols(Math.min(d-e,1));var f=this.faux_grid,g=this.$player.data("coords").coords;this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.last_cols=[],this.last_rows=[],this.collision_api=this.$helper.collision(f,this.options.collision),this.$preview_holder=a("<"+this.$player.get(0).tagName+" />",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:g.width,height:g.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,b,c)},h.on_drag=function(a,b){if(null===this.$player)return!1;var c=this.options.widget_margins[0],d=this.$preview_holder.attr("data-col"),e={left:b.position.left+this.baseX-c*d,top:b.position.top+this.baseY};if(this.options.max_cols===1/0){var f=this.placeholder_grid_data.col+this.placeholder_grid_data.size_x-1;f>=this.cols-1&&this.options.max_cols>=this.cols+1&&(this.add_faux_cols(1),this.set_dom_grid_width(this.cols+1),this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0])),this.collision_api.set_colliders(this.faux_grid)}this.colliders_data=this.collision_api.get_closest_colliders(e),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:b.position.left,top:b.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,a,b)},h.on_stop_drag=function(a,b){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging");var c=this.options.widget_margins[0],d=this.$preview_holder.attr("data-col");b.position.left=b.position.left+this.baseX-c*d,b.position.top=b.position.top+this.baseY,this.colliders_data=this.collision_api.get_closest_colliders(b.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$changed=this.$changed.add(this.$player),this.options.collision.wait_for_mouseup&&this.for_each_cell_occupied(this.placeholder_grid_data,function(a,b){this.is_widget(a,b)&&this.move_widget_down(this.is_widget(a,b),this.placeholder_grid_data.size_y)}),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data);var e=this.placeholder_grid_data.col,f=this.placeholder_grid_data.row;this.set_cells_player_occupies(e,f),this.$player.coords().grid.row=f,this.$player.coords().grid.col=e,this.options.draggable.stop&&this.options.draggable.stop.call(this,a,b),this.$player.addClass("player-revert").removeClass("player").attr({"data-col":e,"data-row":f}).css({left:"",top:""}),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.w_queue={},this.set_dom_grid_height(),this.set_dom_grid_width(),this.options.max_cols===1/0&&this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0])},h.on_start_resize=function(b,c){this.$resized_widget=c.$player.closest(".gs-w"),this.resize_coords=this.$resized_widget.coords(),this.resize_wgd=this.resize_coords.grid,this.resize_initial_width=this.resize_coords.coords.width,this.resize_initial_height=this.resize_coords.coords.height,this.resize_initial_sizex=this.resize_coords.grid.size_x,this.resize_initial_sizey=this.resize_coords.grid.size_y,this.resize_initial_col=this.resize_coords.grid.col,this.resize_last_sizex=this.resize_initial_sizex,this.resize_last_sizey=this.resize_initial_sizey,this.resize_max_size_x=Math.min(this.resize_wgd.max_size_x||this.options.resize.max_size[0],this.options.max_cols-this.resize_initial_col+1),this.resize_max_size_y=this.resize_wgd.max_size_y||this.options.resize.max_size[1],this.resize_min_size_x=this.resize_wgd.min_size_x||this.options.resize.min_size[0]||1,this.resize_min_size_y=this.resize_wgd.min_size_y||this.options.resize.min_size[1]||1,this.resize_initial_last_col=this.get_highest_occupied_cell().col,this.set_dom_grid_width(this.cols),this.resize_dir={right:c.$player.is("."+this.resize_handle_class+"-x"),bottom:c.$player.is("."+this.resize_handle_class+"-y")},this.is_responsive()||this.$resized_widget.css({"min-width":this.options.widget_base_dimensions[0],"min-height":this.options.widget_base_dimensions[1]});var d=this.$resized_widget.get(0).tagName;this.$resize_preview_holder=a("<"+d+" />",{"class":"preview-holder resize-preview-holder","data-row":this.$resized_widget.attr("data-row"), +"data-col":this.$resized_widget.attr("data-col"),css:{width:this.resize_initial_width,height:this.resize_initial_height}}).appendTo(this.$el),this.$resized_widget.addClass("resizing"),this.options.resize.start&&this.options.resize.start.call(this,b,c,this.$resized_widget),this.$el.trigger("gridster:resizestart")},h.on_stop_resize=function(b,c){this.$resized_widget.removeClass("resizing").css({width:"",height:"","min-width":"","min-height":""}),delay(a.proxy(function(){this.$resize_preview_holder.remove().css({"min-width":"","min-height":""}),this.options.resize.stop&&this.options.resize.stop.call(this,b,c,this.$resized_widget),this.$el.trigger("gridster:resizestop")},this),300),this.set_dom_grid_width(),this.set_dom_grid_height(),this.options.max_cols===1/0&&this.drag_api.set_limits(this.cols*this.min_widget_width)},h.on_resize=function(a,b){var c,d=b.pointer.diff_left,e=b.pointer.diff_top,f=this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0],g=this.options.widget_base_dimensions[1],h=this.options.widget_margins[0],i=this.options.widget_margins[1],j=this.resize_max_size_x,k=this.resize_min_size_x,l=this.resize_max_size_y,m=this.resize_min_size_y,n=this.options.max_cols===1/0,o=Math.ceil(d/(f+2*h)-.2),p=Math.ceil(e/(g+2*i)-.2),q=Math.max(1,this.resize_initial_sizex+o),r=Math.max(1,this.resize_initial_sizey+p),s=Math.floor(this.container_width/this.min_widget_width-this.resize_initial_col+1),t=s*this.min_widget_width+(s-1)*h;q=Math.max(Math.min(q,j),k),q=Math.min(s,q),c=j*f+(q-1)*h;var u=Math.min(c,t),v=k*f+(q-1)*h;r=Math.max(Math.min(r,l),m);var w=l*g+(r-1)*i,x=m*g+(r-1)*i;if(this.resize_dir.right?r=this.resize_initial_sizey:this.resize_dir.bottom&&(q=this.resize_initial_sizex),n){var y=this.resize_initial_col+q-1;n&&this.resize_initial_last_col<=y&&(this.set_dom_grid_width(Math.max(y+1,this.cols)),this.colsd;d++)-1===a.inArray(e[d],this.last_cols)&&(b||a.noop).call(this,e[d]);for(d=0;f>d;d++)-1===a.inArray(this.last_cols[d],e)&&(c||a.noop).call(this,this.last_cols[d]);return this.last_cols=e,this},h.on_overlapped_row_change=function(b,c){if(!this.colliders_data.length)return this;var d,e=this.get_targeted_rows(this.colliders_data[0].el.data.row),f=this.last_rows.length,g=e.length;for(d=0;g>d;d++)-1===a.inArray(e[d],this.last_rows)&&(b||a.noop).call(this,e[d]);for(d=0;f>d;d++)-1===a.inArray(this.last_rows[d],e)&&(c||a.noop).call(this,this.last_rows[d]);this.last_rows=e},h.set_player=function(b,c,d){var e=this,f=!1;d||this.empty_cells_player_occupies();var g=d?{col:b}:e.colliders_data[0].el.data,h=g.col,i=g.row||c;this.player_grid_data={col:h,row:i,size_y:this.player_grid_data.size_y,size_x:this.player_grid_data.size_x},this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data);var j=this.get_widgets_overlapped(this.player_grid_data),k=this.player_grid_data.size_y,l=this.player_grid_data.size_x,m=this.cells_occupied_by_placeholder,n=this;if(j.each(a.proxy(function(b,c){var d=a(c),e=d.coords().grid,g=m.cols[0]+l-1,o=m.rows[0]+k-1;if(d.hasClass(n.options.static_class))return!0;if(n.options.collision.wait_for_mouseup&&n.drag_api.is_dragging)n.placeholder_grid_data.col=h,n.placeholder_grid_data.row=i,n.cells_occupied_by_placeholder=n.get_cells_occupied(n.placeholder_grid_data),n.$preview_holder.attr({"data-row":i,"data-col":h});else if(e.size_x<=l&&e.size_y<=k)if(n.is_swap_occupied(m.cols[0],e.row,e.size_x,e.size_y)||n.is_player_in(m.cols[0],e.row)||n.is_in_queue(m.cols[0],e.row,d))if(n.is_swap_occupied(g,e.row,e.size_x,e.size_y)||n.is_player_in(g,e.row)||n.is_in_queue(g,e.row,d))if(n.is_swap_occupied(e.col,m.rows[0],e.size_x,e.size_y)||n.is_player_in(e.col,m.rows[0])||n.is_in_queue(e.col,m.rows[0],d))if(n.is_swap_occupied(e.col,o,e.size_x,e.size_y)||n.is_player_in(e.col,o)||n.is_in_queue(e.col,o,d))if(n.is_swap_occupied(m.cols[0],m.rows[0],e.size_x,e.size_y)||n.is_player_in(m.cols[0],m.rows[0])||n.is_in_queue(m.cols[0],m.rows[0],d))for(var p=0;l>p;p++)for(var q=0;k>q;q++){var r=m.cols[0]+p,s=m.rows[0]+q;if(!n.is_swap_occupied(r,s,e.size_x,e.size_y)&&!n.is_player_in(r,s)&&!n.is_in_queue(r,s,d)){f=n.queue_widget(r,s,d),p=l;break}}else f=n.queue_widget(m.cols[0],m.rows[0],d);else f=n.queue_widget(e.col,o,d);else f=n.queue_widget(e.col,m.rows[0],d);else f=n.queue_widget(g,e.row,d);else f=n.queue_widget(m.cols[0],e.row,d);else n.options.shift_larger_widgets_down&&!f&&j.each(a.proxy(function(b,c){var d=a(c);n.can_go_down(d)&&d.coords().grid.row===n.player_grid_data.row&&(n.move_widget_down(d,n.player_grid_data.size_y),n.set_placeholder(h,i))}));n.clean_up_changed()})),f&&this.can_placeholder_be_set(h,i,l,k)){for(var o in this.w_queue){var p=parseInt(o.split("_")[0]),q=parseInt(o.split("_")[1]);"full"!==this.w_queue[o]&&this.new_move_widget_to(this.w_queue[o],p,q)}this.set_placeholder(h,i)}if(!j.length){if(this.options.shift_widgets_up){var r=this.can_go_player_up(this.player_grid_data);r!==!1&&(i=r)}this.can_placeholder_be_set(h,i,l,k)&&this.set_placeholder(h,i)}return this.w_queue={},{col:h,row:i}},h.is_swap_occupied=function(a,b,c,d){for(var e=!1,f=0;c>f;f++)for(var g=0;d>g;g++){var h=a+f,i=b+g,j=h+"_"+i;if(this.is_occupied(h,i))e=!0;else if(j in this.w_queue){if("full"===this.w_queue[j]){e=!0;continue}var k=this.w_queue[j],l=k.coords().grid;this.is_widget_under_player(l.col,l.row)||delete this.w_queue[j]}i>parseInt(this.options.max_rows)&&(e=!0),h>parseInt(this.options.max_cols)&&(e=!0),this.is_player_in(h,i)&&(e=!0)}return e},h.can_placeholder_be_set=function(a,b,c,d){for(var e=!0,f=0;c>f;f++)for(var g=0;d>g;g++){var h=a+f,i=b+g,j=this.is_widget(h,i);i>parseInt(this.options.max_rows)&&(e=!1),h>parseInt(this.options.max_cols)&&(e=!1),this.is_occupied(h,i)&&!this.is_widget_queued_and_can_move(j)&&(e=!1)}return e},h.queue_widget=function(a,b,c){var d=c,e=d.coords().grid,f=a+"_"+b;if(f in this.w_queue)return!1;this.w_queue[f]=d;for(var g=0;g=0&&a.inArray(c,d.rows)>=0},h.is_placeholder_in=function(b,c){var d=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(b)&&a.inArray(c,d.rows)>=0},h.is_placeholder_in_col=function(b){var c=this.cells_occupied_by_placeholder||[];return a.inArray(b,c.cols)>=0},h.is_empty=function(a,b){return"undefined"!=typeof this.gridmap[a]?"undefined"!=typeof this.gridmap[a][b]&&this.gridmap[a][b]===!1?!0:!1:!0},h.is_valid_col=function(a,b){return this.options.max_cols===1/0?!0:this.cols>=this.calculate_highest_col(a,b)},h.is_valid_row=function(a,b){return this.rows>=this.calculate_highest_row(a,b)},h.calculate_highest_col=function(a,b){return a+(b||1)-1},h.calculate_highest_row=function(a,b){return a+(b||1)-1},h.is_occupied=function(a,b){return this.gridmap[a]&&this.gridmap[a][b]?!0:!1},h.is_widget=function(a,b){var c=this.gridmap[a];return c?(c=c[b],c?c:!1):!1},h.is_static=function(a,b){var c=this.gridmap[a];return c?(c=c[b],c&&c.hasClass(this.options.static_class)?!0:!1):!1},h.is_widget_under_player=function(a,b){return this.is_widget(a,b)?this.is_player_in(a,b):!1},h.get_widgets_under_player=function(b){b||(b=this.cells_occupied_by_player||{cols:[],rows:[]});var c=a([]);return a.each(b.cols,a.proxy(function(d,e){a.each(b.rows,a.proxy(function(a,b){this.is_widget(e,b)&&(c=c.add(this.gridmap[e][b]))},this))},this)),c},h.set_placeholder=function(b,c){var d=a.extend({},this.placeholder_grid_data),e=b+d.size_x-1;e>this.cols&&(b-=e-b);var f=this.placeholder_grid_data.row0&&(this.is_empty(a,h)||this.is_player(a,h)||this.is_widget(a,h)&&g[h].is(f));)d[a].push(h),e=e>h?h:e;return 0===d[a].length?(c=!1,!0):void d[a].sort(function(a,b){return a-b})}),c?this.get_valid_rows(a,d,e):!1},h.can_go_widget_up=function(a){var b=a.row+a.size_y-1,c=!0,d=[],e=1e4;return this.for_each_column_occupied(a,function(f){var g=this.gridmap[f];d[f]=[];for(var h=b+1;--h>0&&(!this.is_widget(f,h)||this.is_player_in(f,h)||g[h].is(a.el));)this.is_player(f,h)||this.is_placeholder_in(f,h)||this.is_player_in(f,h)||d[f].push(h),e>h&&(e=h);return 0===d[f].length?(c=!1,!0):void d[f].sort(function(a,b){return a-b})}),c?this.get_valid_rows(a,d,e):!1},h.get_valid_rows=function(b,c,d){for(var e=b.row,f=b.row+b.size_y-1,g=b.size_y,h=d-1,i=[];++h<=f;){var j=!0;if(a.each(c,function(b,c){a.isArray(c)&&-1===a.inArray(h,c)&&(j=!1)}),j===!0&&(i.push(h),i.length===g))break}var k=!1;return 1===g?i[0]!==e&&(k=i[0]||!1):i[0]!==e&&(k=this.get_consecutive_numbers_index(i,g)),k},h.get_consecutive_numbers_index=function(a,b){for(var c=a.length,d=[],e=!0,f=-1,g=0;c>g;g++){if(e||a[g]===f+1){if(d.push(g),d.length===b)break;e=!1}else d=[],e=!0;f=a[g]}return d.length>=b?a[d[0]]:!1},h.get_widgets_overlapped=function(){var b=a([]),c=[],d=this.cells_occupied_by_player.rows.slice(0);return d.reverse(),a.each(this.cells_occupied_by_player.cols,a.proxy(function(e,f){a.each(d,a.proxy(function(d,e){if(!this.gridmap[f])return!0;var g=this.gridmap[f][e];this.is_occupied(f,e)&&!this.is_player(g)&&-1===a.inArray(g,c)&&(b=b.add(g),c.push(g))},this))},this)),b},h.on_start_overlapping_column=function(a){this.set_player(a,void 0,!1)},h.on_start_overlapping_row=function(a){this.set_player(void 0,a,!1)},h.on_stop_overlapping_column=function(a){var b=this;this.options.shift_larger_widgets_down&&this.for_each_widget_below(a,this.cells_occupied_by_player.rows[0],function(a,c){b.move_widget_up(this,b.player_grid_data.size_y)})},h.on_stop_overlapping_row=function(a){var b=this,c=this.cells_occupied_by_player.cols;if(this.options.shift_larger_widgets_down)for(var d=0,e=c.length;e>d;d++)this.for_each_widget_below(c[d],a,function(a,c){b.move_widget_up(this,b.player_grid_data.size_y)})},h.new_move_widget_to=function(a,b,c){var d=a.coords().grid;return this.remove_from_gridmap(d),d.row=c,d.col=b,this.add_to_gridmap(d),a.attr("data-row",c),a.attr("data-col",b),this.update_widget_position(d,a),this.$changed=this.$changed.add(a),this},h.move_widget=function(a,b,c,d){var e=a.coords().grid,f={col:b,row:c,size_x:e.size_x,size_y:e.size_y};return this.mutate_widget_in_gridmap(a,e,f),this.set_dom_grid_height(),this.set_dom_grid_width(),d&&d.call(this,f.col,f.row),a},h.move_widget_to=function(b,c){var d=this,e=b.coords().grid,f=this.widgets_below(b),g=this.can_move_to(e,e.col,c);return g===!1?!1:(this.remove_from_gridmap(e),e.row=c,this.add_to_gridmap(e),b.attr("data-row",c),this.$changed=this.$changed.add(b),f.each(function(b,c){var e=a(c),f=e.coords().grid,g=d.can_go_widget_up(f);g&&g!==f.row&&d.move_widget_to(e,g)}),this)},h.move_widget_up=function(b,c){if(void 0===c)return!1;var d=b.coords().grid,e=d.row,f=[];return c||(c=1),this.can_go_up(b)?void this.for_each_column_occupied(d,function(d){if(-1===a.inArray(b,f)){var g=b.coords().grid,h=e-c;if(h=this.can_go_up_to_row(g,d,h),!h)return!0;this.remove_from_gridmap(g),g.row=h,this.add_to_gridmap(g),b.attr("data-row",g.row),this.$changed=this.$changed.add(b),f.push(b)}}):!1},h.move_widget_down=function(b,c){var d,e,f,g;if(0>=c)return!1;if(d=b.coords().grid,e=d.row,f=[],g=c,!b)return!1;if(-1===a.inArray(b,f)){var h=b.coords().grid,i=e+c,j=this.widgets_below(b);this.remove_from_gridmap(h),j.each(a.proxy(function(b,c){var d=a(c),e=d.coords().grid,f=this.displacement_diff(e,h,g);f>0&&this.move_widget_down(d,f)},this)),h.row=i,this.update_widget_position(h,b),b.attr("data-row",h.row),this.$changed=this.$changed.add(b),f.push(b)}},h.can_go_up_to_row=function(b,c,d){var e,f=!0,g=[],h=b.row;if(this.for_each_column_occupied(b,function(a){for(g[a]=[],e=h;e--&&this.is_empty(a,e)&&!this.is_placeholder_in(a,e);)g[a].push(e);return g[a].length?void 0:(f=!1,!0)}),!f)return!1;for(e=d,e=1;h>e;e++){for(var i=!0,j=0,k=g.length;k>j;j++)g[j]&&-1===a.inArray(e,g[j])&&(i=!1);if(i===!0){f=e;break}}return f},h.displacement_diff=function(a,b,c){var d=a.row,e=[],f=b.row+b.size_y;this.for_each_column_occupied(a,function(a){for(var b=0,c=f;d>c;c++)this.is_empty(a,c)&&(b+=1);e.push(b)});var g=Math.max.apply(Math,e);return c-=g,c>0?c:0},h.widgets_below=function(b){var c=a([]),e=a.isPlainObject(b)?b:b.coords().grid;if(void 0===e)return c;var f=this,g=e.row+e.size_y-1;return this.for_each_column_occupied(e,function(b){f.for_each_widget_below(b,g,function(b,d){return f.is_player(this)||-1!==a.inArray(this,c)?void 0:(c=c.add(this),!0)})}),d.sort_by_row_asc(c)},h.set_cells_player_occupies=function(a,b){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=a,this.placeholder_grid_data.row=b,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},h.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},h.can_go_down=function(b){var c=!0,d=this;return b.hasClass(this.options.static_class)&&(c=!1),this.widgets_below(b).each(function(){a(this).hasClass(d.options.static_class)&&(c=!1)}),c},h.can_go_up=function(a){var b=a.coords().grid,c=b.row,d=c-1,e=!0;return 1===c?!1:(this.for_each_column_occupied(b,function(a){return this.is_occupied(a,d)||this.is_player(a,d)||this.is_placeholder_in(a,d)||this.is_player_in(a,d)?(e=!1,!0):void 0}),e)},h.can_move_to=function(a,b,c){var d=a.el,e={size_y:a.size_y,size_x:a.size_x,col:b,row:c},f=!0;if(this.options.max_cols!==1/0){var g=b+a.size_x-1;if(g>this.cols)return!1}return this.options.max_rows=d;d++)c.push(d);return c},h.get_targeted_rows=function(a){for(var b=(a||this.player_grid_data.row)+(this.player_grid_data.size_y-1),c=[],d=a;b>=d;d++)c.push(d);return c},h.get_cells_occupied=function(b){var c,d={cols:[],rows:[]};for(arguments[1]instanceof a&&(b=arguments[1].coords().grid),c=0;c0&&this.is_widget(d,m)&&-1===a.inArray(g[d][m],l)&&(h=f.call(g[d][m],d,m),l.push(g[d][m]),h)););},"for_each/below":function(){for(m=e+1,i=g[d].length;i>m;m++)this.is_widget(d,m)&&-1===a.inArray(g[d][m],l)&&(h=f.call(g[d][m],d,m),l.push(g[d][m]))}};n[j]&&n[j].call(this)}},h.for_each_widget_above=function(a,b,c){return this._traversing_widgets("for_each","above",a,b,c),this},h.for_each_widget_below=function(a,b,c){return this._traversing_widgets("for_each","below",a,b,c),this},h.get_highest_occupied_cell=function(){for(var a,b=this.gridmap,c=b[1].length,d=[],e=[],f=b.length-1;f>=1;f--)for(a=c-1;a>=1;a--)if(this.is_widget(f,a)){d.push(a),e.push(f);break}return{col:Math.max.apply(Math,e),row:Math.max.apply(Math,d)}},h.get_widgets_in_range=function(b,c,d,e){var f,g,h,i,j=a([]);for(f=d;f>=b;f--)for(g=e;g>=c;g--)h=this.is_widget(f,g),h!==!1&&(i=h.data("coords").grid,i.col>=b&&i.col<=d&&i.row>=c&&i.row<=e&&(j=j.add(h)));return j},h.get_widgets_at_cell=function(a,b){return this.get_widgets_in_range(a,b,a,b)},h.get_widgets_from=function(b,c){var d=a();return b&&(d=d.add(this.$widgets.filter(function(){var c=parseInt(a(this).attr("data-col"));return c===b||c>b}))),c&&(d=d.add(this.$widgets.filter(function(){var b=parseInt(a(this).attr("data-row"));return b===c||b>c}))),d},h.set_dom_grid_height=function(a){if("undefined"==typeof a){var b=this.get_highest_occupied_cell().row;a=(b+1)*this.options.widget_margins[1]+b*this.min_widget_height}return this.container_height=a,this.$el.css("height",this.container_height),this},h.set_dom_grid_width=function(a){"undefined"==typeof a&&(a=this.get_highest_occupied_cell().col);var b=this.options.max_cols===1/0?this.options.max_cols:this.cols;return a=Math.min(b,Math.max(a,this.options.min_cols)),this.container_width=(a+1)*this.options.widget_margins[0]+a*this.min_widget_width,this.is_responsive()?(this.$el.css({"min-width":"100%","max-width":"100%"}),this):(this.$el.css("width",this.container_width),this)},h.is_responsive=function(){return this.options.autogenerate_stylesheet&&"auto"===this.options.widget_base_dimensions[0]&&this.options.max_cols!==1/0},h.get_responsive_col_width=function(){var a=this.cols||this.options.max_cols;return(this.$el[0].scrollWidth-(a+1)*this.options.widget_margins[0])/a},h.resize_responsive_layout=function(){return this.min_widget_width=this.get_responsive_col_width(),this.generate_stylesheet(),this.update_widgets_dimensions(),this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0]),this},h.toggle_collapsed_grid=function(a,b){return a?(this.$widgets.css({"margin-top":b.widget_margins[0],"margin-bottom":b.widget_margins[0],"min-height":b.widget_base_dimensions[1]}),this.$el.addClass("collapsed"),this.resize_api&&this.disable_resize(),this.drag_api&&this.disable()):(this.$widgets.css({"margin-top":"auto","margin-bottom":"auto","min-height":"auto"}),this.$el.removeClass("collapsed"),this.resize_api&&this.enable_resize(),this.drag_api&&this.enable()),this},h.generate_stylesheet=function(b){var c,e="",f=this.is_responsive()&&this.options.responsive_breakpoint&&a(window).width()=0)return!1;for(this.generated_stylesheets.push(g),d.generated_stylesheets.push(g),c=1;c<=b.cols+1;c++)e+=b.namespace+' [data-col="'+c+'"] { left:'+(f?this.options.widget_margins[0]:c*b.widget_margins[0]+(c-1)*b.widget_base_dimensions[0])+"px; }\n";for(c=1;c<=b.rows+1;c++)e+=b.namespace+' [data-row="'+c+'"] { top:'+(c*b.widget_margins[1]+(c-1)*b.widget_base_dimensions[1])+"px; }\n";for(var h=1;h<=b.rows;h++)e+=b.namespace+' [data-sizey="'+h+'"] { height:'+(f?"auto":h*b.widget_base_dimensions[1]+(h-1)*b.widget_margins[1])+(f?"":"px")+"; }\n";for(var i=1;i<=b.cols;i++){var j=i*b.widget_base_dimensions[0]+(i-1)*b.widget_margins[0];e+=b.namespace+' [data-sizex="'+i+'"] { width:'+(f?this.$wrapper.width()-2*this.options.widget_margins[0]:j>this.$wrapper.width()?this.$wrapper.width():j)+"px; }\n"}return this.remove_style_tags(),this.add_style_tag(e)},h.add_style_tag=function(a){var b=document,c="gridster-stylesheet";if(""!==this.options.namespace&&(c=c+"-"+this.options.namespace),!document.getElementById(c)){var d=b.createElement("style");d.id=c,b.getElementsByTagName("head")[0].appendChild(d),d.setAttribute("type","text/css"),d.styleSheet?d.styleSheet.cssText=a:d.appendChild(document.createTextNode(a)),this.remove_style_tags(),this.$style_tags=this.$style_tags.add(d)}return this},h.remove_style_tags=function(){var b=d.generated_stylesheets,c=this.generated_stylesheets;this.$style_tags.remove(),d.generated_stylesheets=a.map(b,function(b){return-1===a.inArray(b,c)?b:void 0})},h.generate_faux_grid=function(a,b){this.faux_grid=[],this.gridmap=[];var c,d;for(c=b;c>0;c--)for(this.gridmap[c]=[],d=a;d>0;d--)this.add_faux_cell(d,c);return this},h.add_faux_cell=function(b,c){var d=a({left:this.baseX+(c-1)*this.min_widget_width,top:this.baseY+(b-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:c,row:b,original_col:c,original_row:b}).coords();return a.isArray(this.gridmap[c])||(this.gridmap[c]=[]),"undefined"==typeof this.gridmap[c][b]&&(this.gridmap[c][b]=!1),this.faux_grid.push(d),this},h.add_faux_rows=function(a){a=window.parseInt(a,10);for(var b=this.rows,c=b+parseInt(a||1),d=c;d>b;d--)for(var e=this.cols;e>=1;e--)this.add_faux_cell(d,e);return this.rows=c,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},h.add_faux_cols=function(a){a=window.parseInt(a,10);var b=this.cols,c=b+parseInt(a||1);c=Math.min(c,this.options.max_cols);for(var d=b+1;c>=d;d++)for(var e=this.rows;e>=1;e--)this.add_faux_cell(e,d);return this.cols=c,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},h.recalculate_faux_grid=function(){var b=this.$wrapper.width();return this.baseX=(f.width()-b)/2,this.baseY=this.$wrapper.offset().top,"relative"===this.$wrapper.css("position")&&(this.baseX=this.baseY=0),a.each(this.faux_grid,a.proxy(function(a,b){this.faux_grid[a]=b.update({left:this.baseX+(b.data.col-1)*this.min_widget_width,top:this.baseY+(b.data.row-1)*this.min_widget_height})},this)),this.is_responsive()&&this.resize_responsive_layout(),this.options.center_widgets&&this.center_widgets(),this},h.resize_widget_dimensions=function(b){return b.widget_margins&&(this.options.widget_margins=b.widget_margins),b.widget_base_dimensions&&(this.options.widget_base_dimensions=b.widget_base_dimensions),this.$widgets.each(a.proxy(function(b,c){var d=a(c);this.resize_widget(d)},this)),this.generate_grid_and_stylesheet(),this.get_widgets_from_DOM(),this.set_dom_grid_height(),this.set_dom_grid_width(),this},h.get_widgets_from_DOM=function(){var b=this.$widgets.map(a.proxy(function(b,c){var d=a(c);return this.dom_to_coords(d)},this));b=d.sort_by_row_and_col_asc(b);var c=a(b).map(a.proxy(function(a,b){return this.register_widget(b)||null},this));return c.length&&this.$el.trigger("gridster:positionschanged"),this},h.get_num_widgets=function(){return this.$widgets.size()},h.set_num_columns=function(b){var c=this.options.max_cols,d=Math.floor(b/(this.min_widget_width+this.options.widget_margins[0]))+this.options.extra_cols,e=this.$widgets.map(function(){return a(this).attr("data-col")}).get();e.length||(e=[0]);var f=Math.max.apply(Math,e);this.cols=Math.max(f,d,this.options.min_cols),c!==1/0&&c>=f&&c this.player_max_left) { @@ -598,8 +632,10 @@ pointer: { left: mouse_actual_pos.left, top: mouse_actual_pos.top, - diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x), - diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y) + diff_left: diff_x + (this.$scroll_container.scrollLeft() - + this.scroll_container_offset_x), + diff_top: diff_y + (this.$scroll_container.scrollTop() - + this.scroll_container_offset_y) } }; }; @@ -630,28 +666,36 @@ var area_size = 50; var scroll_inc = 30; + var scrollDir = 'scroll' + capitalize(dir_prop); var is_x = axis === 'x'; - var window_size = is_x ? this.window_width : this.window_height; - var doc_size = is_x ? $(document).width() : $(document).height(); + var scroller_size = is_x ? this.scroller_width : this.scroller_height; + var doc_size; + if (this.$scroll_container === window){ + doc_size = is_x ? this.$scroll_container.width() : + this.$scroll_container.height(); + }else{ + doc_size = is_x ? this.$scroll_container[0].scrollWidth : + this.$scroll_container[0].scrollHeight; + } var player_size = is_x ? this.$player.width() : this.$player.height(); var next_scroll; - var scroll_offset = $window['scroll' + capitalize(dir_prop)](); - var min_window_pos = scroll_offset; - var max_window_pos = min_window_pos + window_size; + var scroll_offset = this.$scroll_container[scrollDir](); + var min_scroll_pos = scroll_offset; + var max_scroll_pos = min_scroll_pos + scroller_size; - var mouse_next_zone = max_window_pos - area_size; // down/right - var mouse_prev_zone = min_window_pos + area_size; // up/left + var mouse_next_zone = max_scroll_pos - area_size; // down/right + var mouse_prev_zone = min_scroll_pos + area_size; // up/left - var abs_mouse_pos = min_window_pos + data.pointer[dir_prop]; + var abs_mouse_pos = min_scroll_pos + data.pointer[dir_prop]; - var max_player_pos = (doc_size - window_size + player_size); + var max_player_pos = (doc_size - scroller_size + player_size); if (abs_mouse_pos >= mouse_next_zone) { next_scroll = scroll_offset + scroll_inc; if (next_scroll < max_player_pos) { - $window['scroll' + capitalize(dir_prop)](next_scroll); + this.$scroll_container[scrollDir](next_scroll); this['scroll_offset_' + axis] += scroll_inc; } } @@ -659,7 +703,7 @@ if (abs_mouse_pos <= mouse_prev_zone) { next_scroll = scroll_offset - scroll_inc; if (next_scroll > 0) { - $window['scroll' + capitalize(dir_prop)](next_scroll); + this.$scroll_container[scrollDir](next_scroll); this['scroll_offset_' + axis] -= scroll_inc; } } @@ -674,14 +718,13 @@ }; - fn.calculate_dimensions = function(e) { - this.window_height = $window.height(); - this.window_width = $window.width(); + fn.calculate_dimensions = function() { + this.scroller_height = this.$scroll_container.height(); + this.scroller_width = this.$scroll_container.width(); }; fn.drag_handler = function(e) { - var node = e.target.nodeName; // skip if drag is disabled, or click was not done with the mouse primary button if (this.disabled || e.which !== 1 && !isTouch) { return; @@ -737,7 +780,6 @@ var offset = this.$container.offset(); this.baseX = Math.round(offset.left); this.baseY = Math.round(offset.top); - this.initial_container_width = this.options.container_width || this.$container.width(); if (this.options.helper === 'clone') { this.$helper = this.$player.clone() @@ -747,13 +789,10 @@ this.helper = false; } - this.win_offset_y = $(window).scrollTop(); - this.win_offset_x = $(window).scrollLeft(); - this.scroll_offset_y = 0; - this.scroll_offset_x = 0; + this.scroll_container_offset_y = this.$scroll_container.scrollTop(); + this.scroll_container_offset_x = this.$scroll_container.scrollLeft(); this.el_init_offset = this.$player.offset(); this.player_width = this.$player.width(); - this.player_height = this.$player.height(); this.set_limits(this.options.container_width); @@ -827,7 +866,7 @@ this.$container.off(this.ns); this.$document.off(this.ns); - $(window).off(this.ns); + $window.off(this.ns); $.removeData(this.$container, 'drag'); }; @@ -841,3161 +880,4233 @@ return this.options.ignore_dragging(event); } + if (this.options.resize) { + return ! $(event.target).is(this.options.items); + } + return $(event.target).is(this.options.ignore_dragging.join(', ')); }; //jQuery adapter - $.fn.drag = function ( options ) { + $.fn.gridDraggable = function ( options ) { return new Draggable(this, options); }; + $.fn.dragg = function (options) { + return this.each(function () { + if (!$.data(this, 'drag')) { + $.data(this, 'drag', new Draggable(this, options)); + } + }); + }; + return Draggable; })); -;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { - define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); - } else { - root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, - root.GridsterCollision); - } - - }(this, function($, Draggable, Collision) { - - var defaults = { - namespace: '', - widget_selector: 'li', - widget_margins: [10, 10], - widget_base_dimensions: [400, 225], - extra_rows: 0, - extra_cols: 0, - min_cols: 1, - max_cols: Infinity, - min_rows: 15, - max_size_x: false, - autogrow_cols: false, - autogenerate_stylesheet: true, - avoid_overlapped_widgets: true, - auto_init: true, - serialize_params: function($w, wgd) { - return { - col: wgd.col, - row: wgd.row, - size_x: wgd.size_x, - size_y: wgd.size_y - }; - }, - collision: {}, - draggable: { - items: '.gs-w', - distance: 4, - ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) - }, - resize: { - enabled: false, - axes: ['both'], - handle_append_to: '', - handle_class: 'gs-resize-handle', - max_size: [Infinity, Infinity], - min_size: [1, 1] - } - }; - - /** - * @class Gridster - * @uses Draggable - * @uses Collision - * @param {HTMLElement} el The HTMLelement that contains all the widgets. - * @param {Object} [options] An Object with all options you want to - * overwrite: - * @param {HTMLElement|String} [options.widget_selector] Define who will - * be the draggable widgets. Can be a CSS Selector String or a - * collection of HTMLElements - * @param {Array} [options.widget_margins] Margin between widgets. - * The first index for the horizontal margin (left, right) and - * the second for the vertical margin (top, bottom). - * @param {Array} [options.widget_base_dimensions] Base widget dimensions - * in pixels. The first index for the width and the second for the - * height. - * @param {Number} [options.extra_cols] Add more columns in addition to - * those that have been calculated. - * @param {Number} [options.extra_rows] Add more rows in addition to - * those that have been calculated. - * @param {Number} [options.min_cols] The minimum required columns. - * @param {Number} [options.max_cols] The maximum columns possible (set to null - * for no maximum). - * @param {Number} [options.min_rows] The minimum required rows. - * @param {Number} [options.max_size_x] The maximum number of columns - * that a widget can span. - * @param {Boolean} [options.autogenerate_stylesheet] If true, all the - * CSS required to position all widgets in their respective columns - * and rows will be generated automatically and injected to the - * `` of the document. You can set this to false, and write - * your own CSS targeting rows and cols via data-attributes like so: - * `[data-col="1"] { left: 10px; }` - * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded - * from the DOM can be overlapped. It is helpful if the positions were - * bad stored in the database or if there was any conflict. - * @param {Boolean} [options.auto_init] Automatically call gridster init - * method or not when the plugin is instantiated. - * @param {Function} [options.serialize_params] Return the data you want - * for each widget in the serialization. Two arguments are passed: - * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid - * coords object (`col`, `row`, `size_x`, `size_y`). - * @param {Object} [options.collision] An Object with all options for - * Collision class you want to overwrite. See Collision docs for - * more info. - * @param {Object} [options.draggable] An Object with all options for - * Draggable class you want to overwrite. See Draggable docs for more - * info. - * @param {Object|Function} [options.draggable.ignore_dragging] Note that - * if you use a Function, and resize is enabled, you should ignore the - * resize handlers manually (options.resize.handle_class). - * @param {Object} [options.resize] An Object with resize config options. - * @param {Boolean} [options.resize.enabled] Set to true to enable - * resizing. - * @param {Array} [options.resize.axes] Axes in which widgets can be - * resized. Possible values: ['x', 'y', 'both']. - * @param {String} [options.resize.handle_append_to] Set a valid CSS - * selector to append resize handles to. - * @param {String} [options.resize.handle_class] CSS class name used - * by resize handles. - * @param {Array} [options.resize.max_size] Limit widget dimensions - * when resizing. Array values should be integers: - * `[max_cols_occupied, max_rows_occupied]` - * @param {Array} [options.resize.min_size] Limit widget dimensions - * when resizing. Array values should be integers: - * `[min_cols_occupied, min_rows_occupied]` - * @param {Function} [options.resize.start] Function executed - * when resizing starts. - * @param {Function} [otions.resize.resize] Function executed - * during the resizing. - * @param {Function} [options.resize.stop] Function executed - * when resizing stops. - * - * @constructor - */ - function Gridster(el, options) { - this.options = $.extend(true, {}, defaults, options); - this.$el = $(el); - this.$wrapper = this.$el.parent(); - this.$widgets = this.$el.children( - this.options.widget_selector).addClass('gs-w'); - this.widgets = []; - this.$changed = $([]); - this.wrapper_width = this.$wrapper.width(); - this.min_widget_width = (this.options.widget_margins[0] * 2) + - this.options.widget_base_dimensions[0]; - this.min_widget_height = (this.options.widget_margins[1] * 2) + - this.options.widget_base_dimensions[1]; - - this.generated_stylesheets = []; - this.$style_tags = $([]); - - this.options.auto_init && this.init(); - } - - Gridster.defaults = defaults; - Gridster.generated_stylesheets = []; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in ascending way. - * - * @method sort_by_row_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (!a.row) { - a = $(a).coords().grid; - b = $(b).coords().grid; - } - - if (a.row > b.row) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) placing first the empty cells upper left. - * - * @method sort_by_row_and_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_and_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row > b.row || a.row === b.row && a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects by column (representing the grid - * coords of each widget) in ascending way. - * - * @method sort_by_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method sort_by_row_desc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_desc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row + a.size_y < b.row + b.size_y) { - return 1; - } - return -1; - }); - return widgets; - }; - - - - /** Instance Methods **/ - - var fn = Gridster.prototype; - - fn.init = function() { - this.options.resize.enabled && this.setup_resize(); - this.generate_grid_and_stylesheet(); - this.get_widgets_from_DOM(); - this.set_dom_grid_height(); - this.set_dom_grid_width(); - this.$wrapper.addClass('ready'); - this.draggable(); - this.options.resize.enabled && this.resizable(); - - $(window).bind('resize.gridster', throttle( - $.proxy(this.recalculate_faux_grid, this), 200)); - }; - - - /** - * Disables dragging. - * - * @method disable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.disable = function() { - this.$wrapper.find('.player-revert').removeClass('player-revert'); - this.drag_api.disable(); - return this; - }; - - - /** - * Enables dragging. - * - * @method enable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.enable = function() { - this.drag_api.enable(); - return this; - }; - - - - /** - * Disables drag-and-drop widget resizing. - * - * @method disable - * @return {Class} Returns instance of gridster Class. - */ - fn.disable_resize = function() { - this.$el.addClass('gs-resize-disabled'); - this.resize_api.disable(); - return this; - }; - - - /** - * Enables drag-and-drop widget resizing. - * - * @method enable - * @return {Class} Returns instance of gridster Class. - */ - fn.enable_resize = function() { - this.$el.removeClass('gs-resize-disabled'); - this.resize_api.enable(); - return this; - }; - - - /** - * Add a new widget to the grid. - * - * @method add_widget - * @param {String|HTMLElement} html The string representing the HTML of the widget - * or the HTMLElement. - * @param {Number} [size_x] The nº of rows the widget occupies horizontally. - * @param {Number} [size_y] The nº of columns the widget occupies vertically. - * @param {Number} [col] The column the widget should start in. - * @param {Number} [row] The row the widget should start in. - * @param {Array} [max_size] max_size Maximun size (in units) for width and height. - * @param {Array} [min_size] min_size Minimum size (in units) for width and height. - * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. - * the widget that was just created. - */ - fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) { - var pos; - size_x || (size_x = 1); - size_y || (size_y = 1); - - if (!col & !row) { - pos = this.next_position(size_x, size_y); - } else { - pos = { - col: col, - row: row, - size_x: size_x, - size_y: size_y - }; - - this.empty_cells(col, row, size_x, size_y); - } - - var $w = $(html).attr({ - 'data-col': pos.col, - 'data-row': pos.row, - 'data-sizex' : size_x, - 'data-sizey' : size_y - }).addClass('gs-w').appendTo(this.$el).hide(); - - this.$widgets = this.$widgets.add($w); - - this.register_widget($w); - - this.add_faux_rows(pos.size_y); - //this.add_faux_cols(pos.size_x); - - if (max_size) { - this.set_widget_max_size($w, max_size); - } - - if (min_size) { - this.set_widget_min_size($w, min_size); - } - - this.set_dom_grid_width(); - this.set_dom_grid_height(); - - this.drag_api.set_limits(this.cols * this.min_widget_width); - - return $w.fadeIn(); - }; - - - /** - * Change widget size limits. - * - * @method set_widget_min_size - * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement - * representing the widget or an index representing the desired widget. - * @param {Array} min_size Minimum size (in units) for width and height. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.set_widget_min_size = function($widget, min_size) { - $widget = typeof $widget === 'number' ? - this.$widgets.eq($widget) : $widget; - - if (!$widget.length) { return this; } - - var wgd = $widget.data('coords').grid; - wgd.min_size_x = min_size[0]; - wgd.min_size_y = min_size[1]; - - return this; - }; - - - /** - * Change widget size limits. - * - * @method set_widget_max_size - * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement - * representing the widget or an index representing the desired widget. - * @param {Array} max_size Maximun size (in units) for width and height. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.set_widget_max_size = function($widget, max_size) { - $widget = typeof $widget === 'number' ? - this.$widgets.eq($widget) : $widget; - - if (!$widget.length) { return this; } - - var wgd = $widget.data('coords').grid; - wgd.max_size_x = max_size[0]; - wgd.max_size_y = max_size[1]; - - return this; - }; - - - /** - * Append the resize handle into a widget. - * - * @method add_resize_handle - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.add_resize_handle = function($w) { - var append_to = this.options.resize.handle_append_to; - $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w); - - return this; - }; - - - /** - * Change the size of a widget. Width is limited to the current grid width. - * - * @method resize_widget - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @param {Number} size_x The number of columns that will occupy the widget. - * By default size_x is limited to the space available from - * the column where the widget begins, until the last column to the right. - * @param {Number} size_y The number of rows that will occupy the widget. - * @param {Function} [callback] Function executed when the widget is removed. - * @return {HTMLElement} Returns $widget. - */ - fn.resize_widget = function($widget, size_x, size_y, callback) { - var wgd = $widget.coords().grid; - var col = wgd.col; - var max_cols = this.options.max_cols; - var old_size_y = wgd.size_y; - var old_col = wgd.col; - var new_col = old_col; - - size_x || (size_x = wgd.size_x); - size_y || (size_y = wgd.size_y); - - if (max_cols !== Infinity) { - size_x = Math.min(size_x, max_cols - col + 1); - } - - if (size_y > old_size_y) { - this.add_faux_rows(Math.max(size_y - old_size_y, 0)); - } - - var player_rcol = (col + size_x - 1); - if (player_rcol > this.cols) { - this.add_faux_cols(player_rcol - this.cols); - } - - var new_grid_data = { - col: new_col, - row: wgd.row, - size_x: size_x, - size_y: size_y - }; - - this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); - - this.set_dom_grid_height(); - this.set_dom_grid_width(); - - if (callback) { - callback.call(this, new_grid_data.size_x, new_grid_data.size_y); - } - - return $widget; - }; - - - /** - * Mutate widget dimensions and position in the grid map. - * - * @method mutate_widget_in_gridmap - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget to mutate. - * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). - * @param {Object} new_wgd New widget grid data. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) { - var old_size_x = wgd.size_x; - var old_size_y = wgd.size_y; - - var old_cells_occupied = this.get_cells_occupied(wgd); - var new_cells_occupied = this.get_cells_occupied(new_wgd); - - var empty_cols = []; - $.each(old_cells_occupied.cols, function(i, col) { - if ($.inArray(col, new_cells_occupied.cols) === -1) { - empty_cols.push(col); - } - }); - - var occupied_cols = []; - $.each(new_cells_occupied.cols, function(i, col) { - if ($.inArray(col, old_cells_occupied.cols) === -1) { - occupied_cols.push(col); - } - }); - - var empty_rows = []; - $.each(old_cells_occupied.rows, function(i, row) { - if ($.inArray(row, new_cells_occupied.rows) === -1) { - empty_rows.push(row); - } - }); - - var occupied_rows = []; - $.each(new_cells_occupied.rows, function(i, row) { - if ($.inArray(row, old_cells_occupied.rows) === -1) { - occupied_rows.push(row); - } - }); - - this.remove_from_gridmap(wgd); - - if (occupied_cols.length) { - var cols_to_empty = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget - ]; - this.empty_cells.apply(this, cols_to_empty); - } - - if (occupied_rows.length) { - var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; - this.empty_cells.apply(this, rows_to_empty); - } - - // not the same that wgd = new_wgd; - wgd.col = new_wgd.col; - wgd.row = new_wgd.row; - wgd.size_x = new_wgd.size_x; - wgd.size_y = new_wgd.size_y; - - this.add_to_gridmap(new_wgd, $widget); - - $widget.removeClass('player-revert'); - - //update coords instance attributes - $widget.data('coords').update({ - width: (new_wgd.size_x * this.options.widget_base_dimensions[0] + - ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2), - height: (new_wgd.size_y * this.options.widget_base_dimensions[1] + - ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2) - }); - - $widget.attr({ - 'data-col': new_wgd.col, - 'data-row': new_wgd.row, - 'data-sizex': new_wgd.size_x, - 'data-sizey': new_wgd.size_y - }); - - if (empty_cols.length) { - var cols_to_remove_holes = [ - empty_cols[0], new_wgd.row, - empty_cols.length, - Math.min(old_size_y, new_wgd.size_y), - $widget - ]; - - this.remove_empty_cells.apply(this, cols_to_remove_holes); - } - - if (empty_rows.length) { - var rows_to_remove_holes = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget - ]; - this.remove_empty_cells.apply(this, rows_to_remove_holes); - } - - this.move_widget_up($widget); - - return this; - }; - - - /** - * Move down widgets in cells represented by the arguments col, row, size_x, - * size_y - * - * @method empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} $exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells = function(col, row, size_x, size_y, $exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row - size_y, - size_x: size_x, - size_y: size_y - }); - - $nexts.not($exclude).each($.proxy(function(i, w) { - var wgd = $(w).coords().grid; - if ( !(wgd.row <= (row + size_y - 1))) { return; } - var diff = (row + size_y) - wgd.row; - this.move_widget_down($(w), diff); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Move up widgets below cells represented by the arguments col, row, size_x, - * size_y. - * - * @method remove_empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row, - size_x: size_x, - size_y: size_y - }); - - $nexts.not(exclude).each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), size_y ); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Get the most left column below to add a new widget. - * - * @method next_position - * @param {Number} size_x The nº of rows the widget occupies horizontally. - * @param {Number} size_y The nº of columns the widget occupies vertically. - * @return {Object} Returns a grid coords object representing the future - * widget coords. - */ - fn.next_position = function(size_x, size_y) { - size_x || (size_x = 1); - size_y || (size_y = 1); - var ga = this.gridmap; - var cols_l = ga.length; - var valid_pos = []; - var rows_l; - - for (var c = 1; c < cols_l; c++) { - rows_l = ga[c].length; - for (var r = 1; r <= rows_l; r++) { - var can_move_to = this.can_move_to({ - size_x: size_x, - size_y: size_y - }, c, r); - - if (can_move_to) { - valid_pos.push({ - col: c, - row: r, - size_y: size_y, - size_x: size_x - }); - } - } - } - - if (valid_pos.length) { - return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; - } - return false; - }; - - - /** - * Remove a widget from the grid. - * - * @method remove_widget - * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. - * @param {Boolean|Function} silent If true, widgets below the removed one - * will not move up. If a Function is passed it will be used as callback. - * @param {Function} callback Function executed when the widget is removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_widget = function(el, silent, callback) { - var $el = el instanceof $ ? el : $(el); - var wgd = $el.coords().grid; - - // if silent is a function assume it's a callback - if ($.isFunction(silent)) { - callback = silent; - silent = false; - } - - this.cells_occupied_by_placeholder = {}; - this.$widgets = this.$widgets.not($el); - - var $nexts = this.widgets_below($el); - - this.remove_from_gridmap(wgd); - - $el.fadeOut($.proxy(function() { - $el.remove(); - - if (!silent) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), wgd.size_y ); - }, this)); - } - - this.set_dom_grid_height(); - - if (callback) { - callback.call(this, el); - } - }, this)); - - return this; - }; - - - /** - * Remove all widgets from the grid. - * - * @method remove_all_widgets - * @param {Function} callback Function executed for each widget removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_all_widgets = function(callback) { - this.$widgets.each($.proxy(function(i, el){ - this.remove_widget(el, true, callback); - }, this)); - - return this; - }; - - - /** - * Returns a serialized array of the widgets in the grid. - * - * @method serialize - * @param {HTMLElement} [$widgets] The collection of jQuery wrapped - * HTMLElements you want to serialize. If no argument is passed all widgets - * will be serialized. - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize = function($widgets) { - $widgets || ($widgets = this.$widgets); - - return $widgets.map($.proxy(function(i, widget) { - var $w = $(widget); - return this.options.serialize_params($w, $w.coords().grid); - }, this)).get(); - }; - - - /** - * Returns a serialized array of the widgets that have changed their - * position. - * - * @method serialize_changed - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize_changed = function() { - return this.serialize(this.$changed); - }; - - - /** - * Convert widgets from DOM elements to "widget grid data" Objects. - * - * @method dom_to_coords - * @param {HTMLElement} $widget The widget to be converted. - */ - fn.dom_to_coords = function($widget) { - return { - 'col': parseInt($widget.attr('data-col'), 10), - 'row': parseInt($widget.attr('data-row'), 10), - 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, - 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, - 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, - 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, - 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, - 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, - 'el': $widget - }; - }; - - - /** - * Creates the grid coords object representing the widget an add it to the - * mapped array of positions. - * - * @method register_widget - * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing - * the widget, or an "widget grid data" Object with (col, row, el ...). - * @return {Boolean} Returns true if the widget final position is different - * than the original. - */ - fn.register_widget = function($el) { - var isDOM = $el instanceof jQuery; - var wgd = isDOM ? this.dom_to_coords($el) : $el; - var posChanged = false; - isDOM || ($el = wgd.el); - - var empty_upper_row = this.can_go_widget_up(wgd); - if (empty_upper_row) { - wgd.row = empty_upper_row; - $el.attr('data-row', empty_upper_row); - this.$el.trigger('gridster:positionchanged', [wgd]); - posChanged = true; - } - - if (this.options.avoid_overlapped_widgets && - !this.can_move_to( - {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) - ) { - $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); - $el.attr({ - 'data-col': wgd.col, - 'data-row': wgd.row, - 'data-sizex': wgd.size_x, - 'data-sizey': wgd.size_y - }); - posChanged = true; - } - - // attach Coord object to player data-coord attribute - $el.data('coords', $el.coords()); - // Extend Coord object with grid position info - $el.data('coords').grid = wgd; - - this.add_to_gridmap(wgd, $el); - - this.options.resize.enabled && this.add_resize_handle($el); - - return posChanged; - }; - - - /** - * Update in the mapped array of positions the value of cells represented by - * the grid coords object passed in the `grid_data` param. - * - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped - * HTMLElement, depends if you want to delete an existing position or add - * a new one. - * @method update_widget_position - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.update_widget_position = function(grid_data, value) { - this.for_each_cell_occupied(grid_data, function(col, row) { - if (!this.gridmap[col]) { return this; } - this.gridmap[col][row] = value; - }); - return this; - }; - - - /** - * Remove a widget from the mapped array of positions. - * - * @method remove_from_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_from_gridmap = function(grid_data) { - return this.update_widget_position(grid_data, false); - }; - - - /** - * Add a widget to the mapped array of positions. - * - * @method add_to_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value The value to set in the specified - * position . - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.add_to_gridmap = function(grid_data, value) { - this.update_widget_position(grid_data, value || grid_data.el); - - if (grid_data.el) { - var $widgets = this.widgets_below(grid_data.el); - $widgets.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget)); - }, this)); - } - }; - - - /** - * Make widgets draggable. - * - * @uses Draggable - * @method draggable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.draggable = function() { - var self = this; - var draggable_options = $.extend(true, {}, this.options.draggable, { - offset_left: this.options.widget_margins[0], - offset_top: this.options.widget_margins[1], - container_width: this.cols * this.min_widget_width, - limit: true, - start: function(event, ui) { - self.$widgets.filter('.player-revert') - .removeClass('player-revert'); - - self.$player = $(this); - self.$helper = $(ui.$helper); - - self.helper = !self.$helper.is(self.$player); - - self.on_start_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstart'); - }, - stop: function(event, ui) { - self.on_stop_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstop'); - }, - drag: throttle(function(event, ui) { - self.on_drag.call(self, event, ui); - self.$el.trigger('gridster:drag'); - }, 60) - }); - - this.drag_api = this.$el.drag(draggable_options); - return this; - }; - - - /** - * Bind resize events to get resize working. - * - * @method resizable - * @return {Class} Returns instance of gridster Class. - */ - fn.resizable = function() { - this.resize_api = this.$el.drag({ - items: '.' + this.options.resize.handle_class, - offset_left: this.options.widget_margins[0], - container_width: this.container_width, - move_element: false, - resize: true, - limit: this.options.autogrow_cols ? false : true, - start: $.proxy(this.on_start_resize, this), - stop: $.proxy(function(event, ui) { - delay($.proxy(function() { - this.on_stop_resize(event, ui); - }, this), 120); - }, this), - drag: throttle($.proxy(this.on_resize, this), 60) - }); - - return this; - }; - - - /** - * Setup things required for resizing. Like build templates for drag handles. - * - * @method setup_resize - * @return {Class} Returns instance of gridster Class. - */ - fn.setup_resize = function() { - this.resize_handle_class = this.options.resize.handle_class; - var axes = this.options.resize.axes; - var handle_tpl = ''; - - this.resize_handle_tpl = $.map(axes, function(type) { - return handle_tpl.replace('{type}', type); - }).join(''); - - if ($.isArray(this.options.draggable.ignore_dragging)) { - this.options.draggable.ignore_dragging.push( - '.' + this.resize_handle_class); - } - - return this; - }; - - - /** - * This function is executed when the player begins to be dragged. - * - * @method on_start_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_start_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); - - this.highest_col = this.get_highest_occupied_cell().col; - - this.$player.addClass('player'); - this.player_grid_data = this.$player.coords().grid; - this.placeholder_grid_data = $.extend({}, this.player_grid_data); - - this.set_dom_grid_height(this.$el.height() + - (this.player_grid_data.size_y * this.min_widget_height)); - - this.set_dom_grid_width(this.cols); - - var pgd_sizex = this.player_grid_data.size_x; - var cols_diff = this.cols - this.highest_col; - - if (this.options.autogrow_cols && cols_diff <= pgd_sizex) { - this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); - } - - var colliders = this.faux_grid; - var coords = this.$player.data('coords').coords; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.last_cols = []; - this.last_rows = []; - - // see jquery.collision.js - this.collision_api = this.$helper.collision( - colliders, this.options.collision); - - this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { - 'class': 'preview-holder', - 'data-row': this.$player.attr('data-row'), - 'data-col': this.$player.attr('data-col'), - css: { - width: coords.width, - height: coords.height - } - }).appendTo(this.$el); - - if (this.options.draggable.start) { - this.options.draggable.start.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player is being dragged. - * - * @method on_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_drag = function(event, ui) { - //break if dragstop has been fired - if (this.$player === null) { - return false; - } - - var abs_offset = { - left: ui.position.left + this.baseX, - top: ui.position.top + this.baseY - }; - - // auto grow cols - if (this.options.autogrow_cols) { - var prcol = this.placeholder_grid_data.col + - this.placeholder_grid_data.size_x - 1; - - // "- 1" due to adding at least 1 column in on_start_drag - if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { - this.add_faux_cols(1); - this.set_dom_grid_width(this.cols + 1); - this.drag_api.set_limits(this.container_width); - } - - this.collision_api.set_colliders(this.faux_grid); - } - - this.colliders_data = this.collision_api.get_closest_colliders( - abs_offset); - - this.on_overlapped_column_change( - this.on_start_overlapping_column, this.on_stop_overlapping_column); - - this.on_overlapped_row_change( - this.on_start_overlapping_row, this.on_stop_overlapping_row); - - - if (this.helper && this.$player) { - this.$player.css({ - 'left': ui.position.left, - 'top': ui.position.top - }); - } - - if (this.options.draggable.drag) { - this.options.draggable.drag.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player stops being dragged. - * - * @method on_stop_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_stop_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper) - .removeClass('dragging'); - - ui.position.left = ui.position.left + this.baseX; - ui.position.top = ui.position.top + this.baseY; - this.colliders_data = this.collision_api.get_closest_colliders( - ui.position); - - this.on_overlapped_column_change( - this.on_start_overlapping_column, - this.on_stop_overlapping_column - ); - - this.on_overlapped_row_change( - this.on_start_overlapping_row, - this.on_stop_overlapping_row - ); - - this.$player.addClass('player-revert').removeClass('player') - .attr({ - 'data-col': this.placeholder_grid_data.col, - 'data-row': this.placeholder_grid_data.row - }).css({ - 'left': '', - 'top': '' - }); - - this.$changed = this.$changed.add(this.$player); - - this.cells_occupied_by_player = this.get_cells_occupied( - this.placeholder_grid_data); - this.set_cells_player_occupies( - this.placeholder_grid_data.col, this.placeholder_grid_data.row); - - this.$player.coords().grid.row = this.placeholder_grid_data.row; - this.$player.coords().grid.col = this.placeholder_grid_data.col; - - if (this.options.draggable.stop) { - this.options.draggable.stop.call(this, event, ui); - } - - this.$preview_holder.remove(); - - this.$player = null; - this.$helper = null; - this.placeholder_grid_data = {}; - this.player_grid_data = {}; - this.cells_occupied_by_placeholder = {}; - this.cells_occupied_by_player = {}; - - this.set_dom_grid_height(); - this.set_dom_grid_width(); - - if (this.options.autogrow_cols) { - this.drag_api.set_limits(this.cols * this.min_widget_width); - } - }; - - - /** - * This function is executed every time a widget starts to be resized. - * - * @method on_start_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_start_resize = function(event, ui) { - this.$resized_widget = ui.$player.closest('.gs-w'); - this.resize_coords = this.$resized_widget.coords(); - this.resize_wgd = this.resize_coords.grid; - this.resize_initial_width = this.resize_coords.coords.width; - this.resize_initial_height = this.resize_coords.coords.height; - this.resize_initial_sizex = this.resize_coords.grid.size_x; - this.resize_initial_sizey = this.resize_coords.grid.size_y; - this.resize_initial_col = this.resize_coords.grid.col; - this.resize_last_sizex = this.resize_initial_sizex; - this.resize_last_sizey = this.resize_initial_sizey; - - this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || - this.options.resize.max_size[0], - this.options.max_cols - this.resize_initial_col + 1); - this.resize_max_size_y = this.resize_wgd.max_size_y || - this.options.resize.max_size[1]; - - this.resize_min_size_x = (this.resize_wgd.min_size_x || - this.options.resize.min_size[0] || 1); - this.resize_min_size_y = (this.resize_wgd.min_size_y || - this.options.resize.min_size[1] || 1); - - this.resize_initial_last_col = this.get_highest_occupied_cell().col; - - this.set_dom_grid_width(this.cols); - - this.resize_dir = { - right: ui.$player.is('.' + this.resize_handle_class + '-x'), - bottom: ui.$player.is('.' + this.resize_handle_class + '-y') - }; - - this.$resized_widget.css({ - 'min-width': this.options.widget_base_dimensions[0], - 'min-height': this.options.widget_base_dimensions[1] - }); - - var nodeName = this.$resized_widget.get(0).tagName; - this.$resize_preview_holder = $('<' + nodeName + ' />', { - 'class': 'preview-holder resize-preview-holder', - 'data-row': this.$resized_widget.attr('data-row'), - 'data-col': this.$resized_widget.attr('data-col'), - 'css': { - 'width': this.resize_initial_width, - 'height': this.resize_initial_height - } - }).appendTo(this.$el); - - this.$resized_widget.addClass('resizing'); +(function (root, factory) { + 'use strict'; + if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.draggable.js'), require('./jquery.collision.js'), require('./jquery.coords.js'), require('./utils.js')); + } + else if (typeof define === 'function' && define.amd) { + define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); + } else { + root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, + root.GridsterCollision); + } + +}(this, function ($, Draggable, Collision) { + 'use strict'; + var $window = $(window), + defaults = { + namespace: '', + widget_selector: 'li', + static_class: 'static', + widget_margins: [10, 10], + widget_base_dimensions: [400, 225], + extra_rows: 0, + extra_cols: 0, + min_cols: 1, + max_cols: Infinity, + min_rows: 1, + max_rows: 15, + autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, + auto_init: true, + center_widgets: false, + responsive_breakpoint: false, + scroll_container: window, + shift_larger_widgets_down: true, + shift_widgets_up: true, + show_element: function($el, callback) { + if (callback) { + $el.fadeIn(callback); + } else { + $el.fadeIn(); + } + }, + hide_element: function($el, callback) { + if (callback) { + $el.fadeOut(callback); + } else { + $el.fadeOut(); + } + }, + serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + }, + collision: { + wait_for_mouseup: false + }, + draggable: { + items: '.gs-w:not(.static)', + distance: 4, + ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) + }, + resize: { + enabled: false, + axes: ['both'], + handle_append_to: '', + handle_class: 'gs-resize-handle', + max_size: [Infinity, Infinity], + min_size: [1, 1] + } + }; + + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.max_cols] The maximum columns possible (set to null + * for no maximum). + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Boolean} [options.auto_init] Automatically call gridster init + * method or not when the plugin is instantiated. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Boolean} [options.shift_larger_widgets_down] Determines if how widgets get pushes + * out of the way of the player. If set to false smaller widgets will not move larger + * widgets out of their way . Defaults to true. + * @param {Boolean} [options.shift_widgets_up] Determines if the player will automatically + * condense the grid and not allow a widget to have space above it. Defaults to true. + * @param {Function} [options.show_element] Makes the given element visible. Two arguments are passed: + * `$el`: the jQuery wrapped HTMLElement, and `callback`: a function that is executed + * after the element is made visible. The callback parameter is optional. + * @param {Function} [options.hide_element] Hides the given element. Two arguments are passed: + * `$el`: the jQuery wrapped HTMLElement, and `callback`: a function that is executed + * after the element is hidden. The callback parameter is optional. + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Boolean} [options.collision.wait_for_mouseup] Default is false. + * If true then it will not move colliding widgets during drag, but only on + * mouseup. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more info. + * @param {Object|Function} [options.draggable.ignore_dragging] Note that + * if you use a Function, and resize is enabled, you should ignore the + * resize handlers manually (options.resize.handle_class). + * @param {Object} [options.resize] An Object with resize config options. + * @param {Boolean} [options.resize.enabled] Set to true to enable + * resizing. + * @param {Array} [options.resize.axes] Axes in which widgets can be + * resized. Possible values: ['x', 'y', 'both']. + * @param {String} [options.resize.handle_append_to] Set a valid CSS + * selector to append resize handles to. + * @param {String} [options.resize.handle_class] CSS class name used + * by resize handles. + * @param {Array} [options.resize.max_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[max_cols_occupied, max_rows_occupied]` + * @param {Array} [options.resize.min_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[min_cols_occupied, min_rows_occupied]` + * @param {Function} [options.resize.start] Function executed + * when resizing starts. + * @param {Function} [options.resize.resize] Function executed + * during the resizing. + * @param {Function} [options.resize.stop] Function executed + * when resizing stops. + * + * @constructor + */ + function Gridster (el, options) { + this.options = $.extend(true, {}, defaults, options); + this.options.draggable = this.options.draggable || {}; + this.options.draggable = $.extend(true, {}, this.options.draggable, + {scroll_container: this.options.scroll_container}); + this.$el = $(el); + this.$scroll_container = this.options.scroll_container === window ? + $(window) : this.$el.closest(this.options.scroll_container); + this.$wrapper = this.$el.parent(); + this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs-w'); + this.$changed = $([]); + this.w_queue = {}; + if (this.is_responsive()) { + this.min_widget_width = this.get_responsive_col_width(); + } else { + this.min_widget_width = this.options.widget_base_dimensions[0]; + } + this.min_widget_height = this.options.widget_base_dimensions[1]; + + this.min_col_count = this.options.min_cols; + this.prev_col_count = this.min_col_count; + + this.generated_stylesheets = []; + this.$style_tags = $([]); + + this.options.auto_init && this.init(); + } + + Gridster.defaults = defaults; + Gridster.generated_stylesheets = []; + + /** + * Convert properties to Integer + * + * @param {Object} obj - config object to be converted + * @return {Object} Returns the converted object. + */ + function convInt (obj) { + var props = ['col', 'row', 'size_x', 'size_y']; + var tmp = {}; + for (var i = 0, len = props.length; i < len; i++) { + var prop = props[i]; + if (!(prop in obj)) { + throw new Error('Not exists property `' + prop + '`'); + } + var val = obj[prop]; + if (!val || isNaN(val)) { + throw new Error('Invalid value of `' + prop + '` property'); + } + tmp[prop] = +val; + } + return tmp; + } + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + } + + a = convInt(a); + b = convInt(b); + if (a.row > b.row) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_and_col_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.row > b.row || a.row === b.row && a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_col_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_desc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + + /** Instance Methods **/ + + var fn = Gridster.prototype; + + fn.init = function () { + this.options.resize.enabled && this.setup_resize(); + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + this.$wrapper.addClass('ready'); + this.draggable(); + this.options.resize.enabled && this.resizable(); + + if (this.options.center_widgets) { + setTimeout($.proxy(function () { + this.center_widgets(); + }, this), 0); + } + + $window.bind('resize.gridster', throttle( + $.proxy(this.recalculate_faux_grid, this), 200)); + }; + + + /** + * Disables dragging. + * + * @method disable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.disable = function () { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + return this; + }; + + + /** + * Enables dragging. + * + * @method enable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.enable = function () { + this.drag_api.enable(); + return this; + }; + + + /** + * Disables drag-and-drop widget resizing. + * + * @method disable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.disable_resize = function () { + this.$el.addClass('gs-resize-disabled'); + this.resize_api.disable(); + return this; + }; + + + /** + * Enables drag-and-drop widget resizing. + * + * @method enable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.enable_resize = function () { + this.$el.removeClass('gs-resize-disabled'); + this.resize_api.enable(); + return this; + }; + + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @param {Array} [max_size] max_size Maximun size (in units) for width and height. + * @param {Array} [min_size] min_size Minimum size (in units) for width and height. + * @param {Function} [callback] Function executed after the widget is shown. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function (html, size_x, size_y, col, row, max_size, min_size, callback) { + var pos; + size_x || (size_x = 1); + size_y || (size_y = 1); + + if (!col && !row) { + pos = this.next_position(size_x, size_y); + } else { + pos = { + col: col, + row: row, + size_x: size_x, + size_y: size_y + }; + if (this.options.avoid_overlapped_widgets) { + this.empty_cells(col, row, size_x, size_y); + } + } + + var $w = $(html).attr({ + 'data-col': pos.col, + 'data-row': pos.row, + 'data-sizex': size_x, + 'data-sizey': size_y + }).addClass('gs-w').appendTo(this.$el).hide(); + + this.$widgets = this.$widgets.add($w); + this.$changed = this.$changed.add($w); + + this.register_widget($w); + + var reqRows = parseInt(pos.row) + (parseInt(pos.size_y) - 1); + if (this.rows < reqRows) { + this.add_faux_rows(reqRows - this.rows); + } + + if (max_size) { + this.set_widget_max_size($w, max_size); + } + + if (min_size) { + this.set_widget_min_size($w, min_size); + } + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + + if (this.options.center_widgets) { + setTimeout($.proxy(function () { + this.center_widgets(); + }, this), 0); + } + + this.options.show_element.call(this, $w, callback); + + return $w; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_min_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} min_size Minimum size (in grid units) for width and height. + * @return {Gridster} Returns instance of gridster Class. + */ + fn.set_widget_min_size = function ($widget, min_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { + return this; + } + + var wgd = $widget.data('coords').grid; + wgd.min_size_x = min_size[0]; + wgd.min_size_y = min_size[1]; + + return this; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_max_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} max_size Maximun size (in units) for width and height. + * @return {Gridster} Returns instance of gridster Class. + */ + fn.set_widget_max_size = function ($widget, max_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { + return this; + } + + var wgd = $widget.data('coords').grid; + wgd.max_size_x = max_size[0]; + wgd.max_size_y = max_size[1]; + + return this; + }; + + + /** + * Append the resize handle into a widget. + * + * @method add_resize_handle + * representing the widget. + * @return {HTMLElement} Returns instance of gridster Class. + * @param $w + */ + fn.add_resize_handle = function ($w) { + var $append_to = this.options.resize.handle_append_to ? $(this.options.resize.handle_append_to, $w) : $w; + + if (($append_to.children('span[class~=\'' + this.resize_handle_class + '\']')).length === 0) { + $(this.resize_handle_tpl).appendTo($append_to); + } + + return this; + }; + + + /** + * Change the size of a widget. Width is limited to the current grid width. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} [size_x] The number of columns that will occupy the widget. + * By default size_x is limited to the space available from + * the column where the widget begins, until the last column to the right. + * @param {Number} [size_y] The number of rows that will occupy the widget. + * @param {Function} [callback] Function executed when the widget is removed. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function ($widget, size_x, size_y, callback) { + var wgd = $widget.coords().grid; + + size_x || (size_x = wgd.size_x); + size_y || (size_y = wgd.size_y); + + //ensure the grid has the correct number of rows + if (!this.is_valid_row(wgd.row, size_y)){ + this.add_faux_rows(Math.max(this.calculate_highest_row(wgd.row, size_y) - this.rows, 0)); + } + + //ensure the grid has the correct number of cols + if (!this.is_valid_col(wgd.col, size_y)) { + this.add_faux_cols(Math.max(this.calculate_highest_row(wgd.col, size_x) - this.cols, 0)); + } + + var new_grid_data = { + col: wgd.col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Expand the widget. Width is set to the current grid width. + * + * @method expand_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of cols that will occupy the widget. + * @param {Number} size_y The number of rows that will occupy the widget. + * @param {Number} col The column to resize the widget from. + * @param {Function} [callback] Function executed when the widget is expanded. + * @return {HTMLElement} Returns $widget. + */ + fn.expand_widget = function ($widget, size_x, size_y, col, callback) { + var wgd = $widget.coords().grid; + var max_size_x = Math.floor(($(window).width() - this.options.widget_margins[0] * 2) / this.min_widget_width); + size_x = size_x || Math.min(max_size_x, this.cols); + size_y || (size_y = wgd.size_y); + + var old_size_y = wgd.size_y; + $widget.attr('pre_expand_col', wgd.col); + $widget.attr('pre_expand_sizex', wgd.size_x); + $widget.attr('pre_expand_sizey', wgd.size_y); + var new_col = col || 1; + + if (size_y > old_size_y) { + this.add_faux_rows(Math.max(size_y - old_size_y, 0)); + } + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Collapse the widget to it's pre-expanded size + * + * @method collapse_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Function} [callback] Function executed when the widget is collapsed. + * @return {HTMLElement} Returns $widget. + */ + fn.collapse_widget = function ($widget, callback) { + var wgd = $widget.coords().grid; + var size_x = parseInt($widget.attr('pre_expand_sizex')); + var size_y = parseInt($widget.attr('pre_expand_sizey')); + + var new_col = parseInt($widget.attr('pre_expand_col')); + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Fit the size of a widget to its content (best guess) + * + * @method fit_to_content + * @param $widget {HTMLElement} $widget The jQuery wrapped HTMLElement + * @param max_cols {Number} max number of columns a widget can take up + * @param max_rows {Number} max number of rows a widget can take up + * @param {Function} [callback] Function executed when the widget is fit to content. + * @return {HTMLElement} Returns $widget. + */ + fn.fit_to_content = function ($widget, max_cols, max_rows, callback) { + var wgd = $widget.coords().grid; + var width = this.$wrapper.width(); + var height = this.$wrapper.height(); + var col_size = this.options.widget_base_dimensions[0] + (2 * this.options.widget_margins[0]); + var row_size = this.options.widget_base_dimensions[1] + (2 * this.options.widget_margins[1]); + var best_cols = Math.ceil((width + (2 * this.options.widget_margins[0])) / col_size); + var best_rows = Math.ceil((height + (2 * this.options.widget_margins[1])) / row_size); + + var new_grid_data = { + col: wgd.col, + row: wgd.row, + size_x: Math.min(max_cols, best_cols), + size_y: Math.min(max_rows, best_rows) + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + + /** + * Centers widgets in grid + * + * @method center_widgets + */ + fn.center_widgets = debounce(function () { + var wrapper_width = this.$wrapper.width(); + var col_size; + if (this.is_responsive()) { + col_size = this.get_responsive_col_width(); + } else { + col_size = this.options.widget_base_dimensions[0] + (2 * this.options.widget_margins[0]); + } + var col_count = Math.floor(Math.max(Math.floor(wrapper_width / col_size), this.min_col_count) / 2) * 2; + + this.options.min_cols = col_count; + this.options.max_cols = col_count; + this.options.extra_cols = 0; + this.set_dom_grid_width(col_count); + this.cols = col_count; + + var col_dif = (col_count - this.prev_col_count) / 2; + + if (col_dif < 0) { + if (this.get_min_col() > col_dif * -1) { + this.shift_cols(col_dif); + } else { + this.resize_widget_dimensions(this.options); + } + + setTimeout($.proxy(function () { + this.resize_widget_dimensions(this.options); + }, this), 0); + + } else if (col_dif > 0) { + this.resize_widget_dimensions(this.options); + + setTimeout($.proxy(function () { + this.shift_cols(col_dif); + }, this), 0); + + } else { + this.resize_widget_dimensions(this.options); + + setTimeout($.proxy(function () { + this.resize_widget_dimensions(this.options); + }, this), 0); + + } + + this.prev_col_count = col_count; + return this; + }, 200); + + + fn.get_min_col = function () { + return Math.min.apply(Math, this.$widgets.map($.proxy(function (key, widget) { + return this.get_cells_occupied($(widget).coords().grid).cols; + }, this)).get()); + }; + + + fn.shift_cols = function (col_dif) { + var widgets_coords = this.$widgets.map($.proxy(function (i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + widgets_coords.each($.proxy(function (i, widget) { + var $widget = $(widget.el); + var wgd = $widget.coords().grid; + var col = parseInt($widget.attr('data-col')); + + var new_grid_data = { + col: Math.max(Math.round(col + col_dif), 1), + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + setTimeout($.proxy(function () { + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + }, this), 0); + }, this)); + }; + + + /** + * Change the dimensions of widgets. + * + * @method resize_widget_dimensions + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.resize_widget_dimensions = function (options) { + if (options.widget_margins) { + this.options.widget_margins = options.widget_margins; + } + + if (options.widget_base_dimensions) { + this.options.widget_base_dimensions = options.widget_base_dimensions; + } + + this.min_widget_width = (this.options.widget_margins[0] * 2) + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + this.options.widget_base_dimensions[1]; + + this.$widgets.each($.proxy(function (i, widget) { + var $widget = $(widget); + this.resize_widget($widget); + }, this)); + + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Mutate widget dimensions and position in the grid map. + * + * @method mutate_widget_in_gridmap + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget to mutate. + * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @param {Object} new_wgd New widget grid data. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.mutate_widget_in_gridmap = function ($widget, wgd, new_wgd) { + var old_size_y = wgd.size_y; + + var old_cells_occupied = this.get_cells_occupied(wgd); + var new_cells_occupied = this.get_cells_occupied(new_wgd); + + //find the cells that this widget currently occupies + var empty_cols = []; + $.each(old_cells_occupied.cols, function (i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + } + }); + + //find the cells that this widget will occupy + var occupied_cols = []; + $.each(new_cells_occupied.cols, function (i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + //find the rows that it currently occupies + var empty_rows = []; + $.each(old_cells_occupied.rows, function (i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + } + }); + + //find the rows that it will occupy + var occupied_rows = []; + $.each(new_cells_occupied.rows, function (i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + } + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget + ]; + this.empty_cells.apply(this, cols_to_empty); + } + + if (occupied_rows.length) { + var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + } + + // not the same that wgd = new_wgd; + wgd.col = new_wgd.col; + wgd.row = new_wgd.row; + wgd.size_x = new_wgd.size_x; + wgd.size_y = new_wgd.size_y; + + this.add_to_gridmap(new_wgd, $widget); + + $widget.removeClass('player-revert'); + + this.update_widget_dimensions($widget, new_wgd); + + if (empty_cols.length) { + var cols_to_remove_holes = [ + empty_cols[0], new_wgd.row, + empty_cols[empty_cols.length - 1] - empty_cols[0] + 1, + Math.min(old_size_y, new_wgd.size_y), + $widget + ]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget + ]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + this.move_widget_up($widget); + + return this; + }; + + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} [$exclude] Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function (col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row - size_y, + size_x: size_x, + size_y: size_y + }); + + $nexts.not($exclude).each($.proxy(function (i, w) { + var $w = $(w), + wgd = $w.coords().grid; + /*jshint -W018 */ + if (!(wgd.row <= (row + size_y - 1))) { + return; + } + /*jshint +W018 */ + var diff = (row + size_y) - wgd.row; + this.move_widget_down($w, diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} exclude Exclude widgets from being moved. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function (col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row, + size_x: size_x, + size_y: size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), size_y ); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function (size_x, size_y) { + size_x || (size_x = 1); + size_y || (size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + var rows_l; + + for (var c = 1; c < cols_l; c++) { + rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x: size_x, + size_y: size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col: c, + row: r, + size_y: size_y, + size_x: size_x + }); + } + } + } + + if (valid_pos.length) { + return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + fn.remove_by_grid = function (col, row) { + var $w = this.is_widget(col, row); + if ($w) { + this.remove_widget($w); + } + }; + + + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {Boolean|Function} [silent] If true, widgets below the removed one + * will not move up. If a Function is passed it will be used as callback. + * @param {Function} [callback] Function executed after the widget is removed. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function (el, silent, callback) { + var $el = el instanceof $ ? el : $(el); + if ($el.length === 0) { + //there is nothing to remove, so we can't remove it + return this; + } + var wgd = $el.coords().grid; + if (wgd === undefined) { + //there is no grid, so we can't remove it + return this; + } + + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + this.options.hide_element.call(this, $el, $.proxy(function(){ + $el.remove(); + + if (!silent) { + $nexts.each($.proxy(function (i, widget) { + this.move_widget_up($(widget), wgd.size_y); + }, this)); + } + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + + return this; + }; + + + /** + * Remove all widgets from the grid. + * + * @method remove_all_widgets + * @param {Function} callback Function executed for each widget removed. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_all_widgets = function (callback) { + this.$widgets.each($.proxy(function (i, el) { + this.remove_widget(el, true, callback); + }, this)); + + return this; + }; + + + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function ($widgets) { + $widgets || ($widgets = this.$widgets); + var result = []; + $widgets.each($.proxy(function (i, widget) { + var $w = $(widget); + if (typeof($w.coords().grid) !== 'undefined') { + result.push(this.options.serialize_params($w, $w.coords().grid)); + } + }, this)); + return result; + }; + + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function () { + return this.serialize(this.$changed); + }; + + + /** + * Convert widgets from DOM elements to "widget grid data" Objects. + * + * @method dom_to_coords + * @param {HTMLElement} $widget The widget to be converted. + */ + fn.dom_to_coords = function ($widget) { + return { + 'col': parseInt($widget.attr('data-col'), 10), + 'row': parseInt($widget.attr('data-row'), 10), + 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, + 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, + 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, + 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, + 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, + 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, + 'el': $widget + }; + }; + + /** + * Creates the grid coords object representing the widget an add it to the + * mapped array of positions. + * + * @method register_widget + * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing + * the widget, or an "widget grid data" Object with (col, row, el ...). + * @return {Boolean} Returns true if the widget final position is different + * than the original. + */ + fn.register_widget = function ($el) { + var isDOM = $el instanceof $; + var wgd = isDOM ? this.dom_to_coords($el) : $el; + var posChanged = false; + isDOM || ($el = wgd.el); + + var empty_upper_row = this.can_go_widget_up(wgd); + if (this.options.shift_widgets_up && empty_upper_row) { + wgd.row = empty_upper_row; + $el.attr('data-row', empty_upper_row); + this.$el.trigger('gridster:positionchanged', [wgd]); + posChanged = true; + } + + if (this.options.avoid_overlapped_widgets && !this.can_move_to( + {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) + ) { + $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); + $el.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + posChanged = true; + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + this.update_widget_dimensions($el, wgd); + + this.options.resize.enabled && this.add_resize_handle($el); + + return posChanged; + }; + + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function (grid_data, value) { + this.for_each_cell_occupied(grid_data, function (col, row) { + if (!this.gridmap[col]) { + return this; + } + this.gridmap[col][row] = value; + }); + return this; + }; + + + /** + * Update the width and height for a widgets coordinate data. + * + * @param {HTMLElement} $widget The widget to update. + * @param wgd {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @method update_widget_dimensions + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widget_dimensions = function ($widget, wgd) { + + var width = (wgd.size_x * (this.is_responsive() ? this.get_responsive_col_width() : this.options.widget_base_dimensions[0]) + + ((wgd.size_x - 1) * this.options.widget_margins[0])); + + var height = (wgd.size_y * this.options.widget_base_dimensions[1] + + ((wgd.size_y - 1) * this.options.widget_margins[1])); + + $widget.data('coords').update({ + width: width, + height: height + }); + + $widget.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + + return this; + }; + + + /** + * Update dimensions for all widgets in the grid. + * + * @method update_widgets_dimensions + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widgets_dimensions = function () { + $.each(this.$widgets, $.proxy(function (idx, widget) { + var wgd = $(widget).coords().grid; + if (typeof (wgd) !== 'object') { + return; + } + this.update_widget_dimensions($(widget), wgd); + }, this)); + return this; + }; + + + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function (grid_data) { + return this.update_widget_position(grid_data, false); + }; + + + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} [value] The value to set in the specified + * position . + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function (grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + }; + + + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.draggable = function () { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left: this.options.widget_margins[0], + offset_top: this.options.widget_margins[1], + container_width: (this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0]), + limit: true, + start: function (event, ui) { + self.$widgets.filter('.player-revert') + .removeClass('player-revert'); + + self.$player = $(this); + self.$helper = $(ui.$helper); + + self.helper = !self.$helper.is(self.$player); + + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop: function (event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag: throttle(function (event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); + + //this.drag_api = this.$el.gridDraggable(draggable_options); + this.drag_api = this.$el.dragg(draggable_options).data('drag'); + }; + + + /** + * Bind resize events to get resize working. + * + * @method resizable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.resizable = function () { + this.resize_api = this.$el.gridDraggable({ + items: '.' + this.options.resize.handle_class, + offset_left: this.options.widget_margins[0], + container_width: this.container_width, + move_element: false, + resize: true, + limit: this.options.max_cols !== Infinity, + scroll_container: this.options.scroll_container, + start: $.proxy(this.on_start_resize, this), + stop: $.proxy(function (event, ui) { + delay($.proxy(function () { + this.on_stop_resize(event, ui); + }, this), 120); + }, this), + drag: throttle($.proxy(this.on_resize, this), 60) + }); + + return this; + }; + + + /** + * Setup things required for resizing. Like build templates for drag handles. + * + * @method setup_resize + * @return {Gridster} Returns instance of gridster Class. + */ + fn.setup_resize = function () { + this.resize_handle_class = this.options.resize.handle_class; + var axes = this.options.resize.axes; + var handle_tpl = ''; + + this.resize_handle_tpl = $.map(axes, function (type) { + return handle_tpl.replace('{type}', type); + }).join(''); + + if ($.isArray(this.options.draggable.ignore_dragging)) { + this.options.draggable.ignore_dragging.push( + '.' + this.resize_handle_class); + } + + + return this; + }; + + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_drag = function (event, ui) { + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.highest_col = this.get_highest_occupied_cell().col; + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + this.set_dom_grid_height(this.$el.height() + + (this.player_grid_data.size_y * this.min_widget_height)); + + this.set_dom_grid_width(this.cols); + + var pgd_sizex = this.player_grid_data.size_x; + var cols_diff = this.cols - this.highest_col; + + if (this.options.max_cols === Infinity && cols_diff <= pgd_sizex) { + this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); + } + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied(this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied(this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + // see jquery.collision.js + this.collision_api = this.$helper.collision(colliders, this.options.collision); + + this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { + 'class': 'preview-holder', + 'data-row': this.$player.attr('data-row'), + 'data-col': this.$player.attr('data-col'), + css: { + width: coords.width, + height: coords.height + } + }).appendTo(this.$el); + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_drag = function (event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + } + + var margin_sides = this.options.widget_margins[0]; + + var placeholder_column = this.$preview_holder.attr('data-col'); + + var abs_offset = { + left: ui.position.left + this.baseX - (margin_sides * placeholder_column), + top: ui.position.top + this.baseY + }; + + // auto grow cols + if (this.options.max_cols === Infinity) { + var prcol = this.placeholder_grid_data.col + + this.placeholder_grid_data.size_x - 1; + + // "- 1" due to adding at least 1 column in on_start_drag + if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { + this.add_faux_cols(1); + this.set_dom_grid_width(this.cols + 1); + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + + this.collision_api.set_colliders(this.faux_grid); + } + + this.colliders_data = this.collision_api.get_closest_colliders(abs_offset); + + this.on_overlapped_column_change(this.on_start_overlapping_column, this.on_stop_overlapping_column); + + this.on_overlapped_row_change(this.on_start_overlapping_row, this.on_stop_overlapping_row); + + if (this.helper && this.$player) { + this.$player.css({ + 'left': ui.position.left, + 'top': ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_drag = function (event, ui) { + this.$helper.add(this.$player).add(this.$wrapper) + .removeClass('dragging'); + + var margin_sides = this.options.widget_margins[0]; + + var placeholder_column = this.$preview_holder.attr('data-col'); + + ui.position.left = ui.position.left + this.baseX - (margin_sides * placeholder_column); + ui.position.top = ui.position.top + this.baseY; + this.colliders_data = this.collision_api.get_closest_colliders( + ui.position); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + this.$changed = this.$changed.add(this.$player); + + // move the cells down if there is an overlap and we are in static mode + if (this.options.collision.wait_for_mouseup) { + this.for_each_cell_occupied(this.placeholder_grid_data, function (tcol, trow) { + if (this.is_widget(tcol, trow)) { + this.move_widget_down(this.is_widget(tcol, trow), this.placeholder_grid_data.size_y); + } + }); + } + + this.cells_occupied_by_player = this.get_cells_occupied(this.placeholder_grid_data); + + var col = this.placeholder_grid_data.col; + var row = this.placeholder_grid_data.row; + + this.set_cells_player_occupies(col, row); + this.$player.coords().grid.row = row; + this.$player.coords().grid.col = col; + + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); + } + + this.$player.addClass('player-revert').removeClass('player') + .attr({ + 'data-col': col, + 'data-row': row + }).css({ + 'left': '', + 'top': '' + }); + + this.$preview_holder.remove(); + + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + this.w_queue = {}; + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (this.options.max_cols === Infinity) { + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + }; + + + /** + * This function is executed every time a widget starts to be resized. + * + * @method on_start_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_resize = function (event, ui) { + this.$resized_widget = ui.$player.closest('.gs-w'); + this.resize_coords = this.$resized_widget.coords(); + this.resize_wgd = this.resize_coords.grid; + this.resize_initial_width = this.resize_coords.coords.width; + this.resize_initial_height = this.resize_coords.coords.height; + this.resize_initial_sizex = this.resize_coords.grid.size_x; + this.resize_initial_sizey = this.resize_coords.grid.size_y; + this.resize_initial_col = this.resize_coords.grid.col; + this.resize_last_sizex = this.resize_initial_sizex; + this.resize_last_sizey = this.resize_initial_sizey; + + this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || this.options.resize.max_size[0], + this.options.max_cols - this.resize_initial_col + 1); + this.resize_max_size_y = this.resize_wgd.max_size_y || this.options.resize.max_size[1]; + + this.resize_min_size_x = (this.resize_wgd.min_size_x || this.options.resize.min_size[0] || 1); + this.resize_min_size_y = (this.resize_wgd.min_size_y || this.options.resize.min_size[1] || 1); + + this.resize_initial_last_col = this.get_highest_occupied_cell().col; + + this.set_dom_grid_width(this.cols); + + this.resize_dir = { + right: ui.$player.is('.' + this.resize_handle_class + '-x'), + bottom: ui.$player.is('.' + this.resize_handle_class + '-y') + }; + + if (!this.is_responsive()) { + this.$resized_widget.css({ + 'min-width': this.options.widget_base_dimensions[0], + 'min-height': this.options.widget_base_dimensions[1] + }); + } + + var nodeName = this.$resized_widget.get(0).tagName; + this.$resize_preview_holder = $('<' + nodeName + ' />', { + 'class': 'preview-holder resize-preview-holder', + 'data-row': this.$resized_widget.attr('data-row'), + 'data-col': this.$resized_widget.attr('data-col'), + 'css': { + 'width': this.resize_initial_width, + 'height': this.resize_initial_height + } + }).appendTo(this.$el); + + this.$resized_widget.addClass('resizing'); if (this.options.resize.start) { - this.options.resize.start.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resizestart'); - }; - - - /** - * This function is executed every time a widget stops being resized. - * - * @method on_stop_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_stop_resize = function(event, ui) { - this.$resized_widget - .removeClass('resizing') - .css({ - 'width': '', - 'height': '' - }); - - delay($.proxy(function() { - this.$resize_preview_holder - .remove() - .css({ - 'min-width': '', - 'min-height': '' - }); - - if (this.options.resize.stop) { - this.options.resize.stop.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resizestop'); - }, this), 300); - - this.set_dom_grid_width(); - - if (this.options.autogrow_cols) { - this.drag_api.set_limits(this.cols * this.min_widget_width); - } - }; - - - /** - * This function is executed when a widget is being resized. - * - * @method on_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_resize = function(event, ui) { - var rel_x = (ui.pointer.diff_left); - var rel_y = (ui.pointer.diff_top); - var wbd_x = this.options.widget_base_dimensions[0]; - var wbd_y = this.options.widget_base_dimensions[1]; - var margin_x = this.options.widget_margins[0]; - var margin_y = this.options.widget_margins[1]; - var max_size_x = this.resize_max_size_x; - var min_size_x = this.resize_min_size_x; - var max_size_y = this.resize_max_size_y; - var min_size_y = this.resize_min_size_y; - var autogrow = this.options.autogrow_cols; - var width; - var max_width = Infinity; - var max_height = Infinity; - - var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); - var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); - - var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); - var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); - - var max_cols = (this.container_width / this.min_widget_width) - - this.resize_initial_col + 1; - var limit_width = ((max_cols * this.min_widget_width) - margin_x * 2); - - size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); - size_x = Math.min(max_cols, size_x); - width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); - max_width = Math.min(width, limit_width); - min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); - - size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); - max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); - min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); - - if (this.resize_dir.right) { - size_y = this.resize_initial_sizey; - } else if (this.resize_dir.bottom) { - size_x = this.resize_initial_sizex; - } - - if (autogrow) { - var last_widget_col = this.resize_initial_col + size_x - 1; - if (autogrow && this.resize_initial_last_col <= last_widget_col) { - this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); - - if (this.cols < last_widget_col) { - this.add_faux_cols(last_widget_col - this.cols); - } - } - } - - var css_props = {}; - !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( - this.resize_initial_width + rel_x, max_width), min_width)); - !this.resize_dir.right && (css_props.height = Math.max(Math.min( - this.resize_initial_height + rel_y, max_height), min_height)); - - this.$resized_widget.css(css_props); - - if (size_x !== this.resize_last_sizex || - size_y !== this.resize_last_sizey) { - - this.resize_widget(this.$resized_widget, size_x, size_y); - this.set_dom_grid_width(this.cols); - - this.$resize_preview_holder.css({ - 'width': '', - 'height': '' - }).attr({ - 'data-row': this.$resized_widget.attr('data-row'), - 'data-sizex': size_x, - 'data-sizey': size_y - }); - } - - if (this.options.resize.resize) { - this.options.resize.resize.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resize'); - - this.resize_last_sizex = size_x; - this.resize_last_sizey = size_y; - }; - - - /** - * Executes the callbacks passed as arguments when a column begins to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new column - * begins to be overlapped. The column is passed as first argument. - * @param {Function} stop_callback Function executed when a column stops - * being overlapped. The column is passed as first argument. - * @method on_overlapped_column_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_column_change = function(start_callback, stop_callback) { - if (!this.colliders_data.length) { - return this; - } - var cols = this.get_targeted_columns( - this.colliders_data[0].el.data.col); - - var last_n_cols = this.last_cols.length; - var n_cols = cols.length; - var i; - - for (i = 0; i < n_cols; i++) { - if ($.inArray(cols[i], this.last_cols) === -1) { - (start_callback || $.noop).call(this, cols[i]); - } - } - - for (i = 0; i< last_n_cols; i++) { - if ($.inArray(this.last_cols[i], cols) === -1) { - (stop_callback || $.noop).call(this, this.last_cols[i]); - } - } - - this.last_cols = cols; - - return this; - }; - - - /** - * Executes the callbacks passed as arguments when a row starts to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new row begins - * to be overlapped. The row is passed as first argument. - * @param {Function} end_callback Function executed when a row stops being - * overlapped. The row is passed as first argument. - * @method on_overlapped_row_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_row_change = function(start_callback, end_callback) { - if (!this.colliders_data.length) { - return this; - } - var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); - var last_n_rows = this.last_rows.length; - var n_rows = rows.length; - var i; - - for (i = 0; i < n_rows; i++) { - if ($.inArray(rows[i], this.last_rows) === -1) { - (start_callback || $.noop).call(this, rows[i]); - } - } - - for (i = 0; i < last_n_rows; i++) { - if ($.inArray(this.last_rows[i], rows) === -1) { - (end_callback || $.noop).call(this, this.last_rows[i]); - } - } - - this.last_rows = rows; - }; - - - /** - * Sets the current position of the player - * - * @param {Number} col - * @param {Number} row - * @param {Boolean} no_player - * @method set_player - * @return {object} - */ - fn.set_player = function(col, row, no_player) { - var self = this; - if (!no_player) { - this.empty_cells_player_occupies(); - } - var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; - var to_col = cell.col; - var to_row = row || cell.row; - - this.player_grid_data = { - col: to_col, - row: to_row, - size_y : this.player_grid_data.size_y, - size_x : this.player_grid_data.size_x - }; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - - var $overlapped_widgets = this.get_widgets_overlapped( - this.player_grid_data); - - var constraints = this.widgets_constraints($overlapped_widgets); - - this.manage_movements(constraints.can_go_up, to_col, to_row); - this.manage_movements(constraints.can_not_go_up, to_col, to_row); - - /* if there is not widgets overlapping in the new player position, - * update the new placeholder position. */ - if (!$overlapped_widgets.length) { - var pp = this.can_go_player_up(this.player_grid_data); - if (pp !== false) { - to_row = pp; - } - this.set_placeholder(to_col, to_row); - } - - return { - col: to_col, - row: to_row - }; - }; - - - /** - * See which of the widgets in the $widgets param collection can go to - * a upper row and which not. - * - * @method widgets_contraints - * @param {jQuery} $widgets A jQuery wrapped collection of - * HTMLElements. - * @return {object} Returns a literal Object with two keys: `can_go_up` & - * `can_not_go_up`. Each contains a set of HTMLElements. - */ - fn.widgets_constraints = function($widgets) { - var $widgets_can_go_up = $([]); - var $widgets_can_not_go_up; - var wgd_can_go_up = []; - var wgd_can_not_go_up = []; - - $widgets.each($.proxy(function(i, w) { - var $w = $(w); - var wgd = $w.coords().grid; - if (this.can_go_widget_up(wgd)) { - $widgets_can_go_up = $widgets_can_go_up.add($w); - wgd_can_go_up.push(wgd); - } else { - wgd_can_not_go_up.push(wgd); - } - }, this)); - - $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); - - return { - can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), - can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) - }; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method manage_movements - * @param {jQuery} $widgets A jQuery collection of HTMLElements - * representing the widgets you want to move. - * @param {Number} to_col The column to which we want to move the widgets. - * @param {Number} to_row The row to which we want to move the widgets. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.manage_movements = function($widgets, to_col, to_row) { - $.each($widgets, $.proxy(function(i, w) { - var wgd = w; - var $w = wgd.el; - - var can_go_widget_up = this.can_go_widget_up(wgd); - - if (can_go_widget_up) { - //target CAN go up - //so move widget up - this.move_widget_to($w, can_go_widget_up); - this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); - - } else { - //target can't go up - var can_go_player_up = this.can_go_player_up( - this.player_grid_data); - - if (!can_go_player_up) { - // target can't go up - // player cant't go up - // so we need to move widget down to a position that dont - // overlaps player - var y = (to_row + this.player_grid_data.size_y) - wgd.row; - - this.move_widget_down($w, y); - this.set_placeholder(to_col, to_row); - } - } - }, this)); - - return this; - }; - - /** - * Determines if there is a widget in the row and col given. Or if the - * HTMLElement passed as first argument is the player. - * - * @method is_player - * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of - * HTMLElements. - * @param {Number} [row] The column to which we want to move the widgets. - * @return {Boolean} Returns true or false. - */ - fn.is_player = function(col_or_el, row) { - if (row && !this.gridmap[col_or_el]) { return false; } - var $w = row ? this.gridmap[col_or_el][row] : col_or_el; - return $w && ($w.is(this.$player) || $w.is(this.$helper)); - }; - - - /** - * Determines if the widget that is being dragged is currently over the row - * and col given. - * - * @method is_player_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_player_in = function(col, row) { - var c = this.cells_occupied_by_player || {}; - return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; - }; - - - /** - * Determines if the placeholder is currently over the row and col given. - * - * @method is_placeholder_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in = function(col, row) { - var c = this.cells_occupied_by_placeholder || {}; - return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; - }; - - - /** - * Determines if the placeholder is currently over the column given. - * - * @method is_placeholder_in_col - * @param {Number} col The column to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in_col = function(col) { - var c = this.cells_occupied_by_placeholder || []; - return $.inArray(col, c.cols) >= 0; - }; - - - /** - * Determines if the cell represented by col and row params is empty. - * - * @method is_empty - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_empty = function(col, row) { - if (typeof this.gridmap[col] !== 'undefined') { - if(typeof this.gridmap[col][row] !== 'undefined' && - this.gridmap[col][row] === false + this.options.resize.start.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestart'); + }; + + + /** + * This function is executed every time a widget stops being resized. + * + * @method on_stop_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_resize = function (event, ui) { + this.$resized_widget + .removeClass('resizing') + .css({ + 'width': '', + 'height': '', + 'min-width': '', + 'min-height': '' + }); + + delay($.proxy(function () { + this.$resize_preview_holder + .remove() + .css({ + 'min-width': '', + 'min-height': '' + }); + + if (this.options.resize.stop) { + this.options.resize.stop.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestop'); + }, this), 300); + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + if (this.options.max_cols === Infinity) { + this.drag_api.set_limits(this.cols * this.min_widget_width); + } + }; + + + /** + * This function is executed when a widget is being resized. + * + * @method on_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_resize = function (event, ui) { + var rel_x = (ui.pointer.diff_left); + var rel_y = (ui.pointer.diff_top); + var wbd_x = this.is_responsive() ? this.get_responsive_col_width() : this.options.widget_base_dimensions[0]; + var wbd_y = this.options.widget_base_dimensions[1]; + var margin_x = this.options.widget_margins[0]; + var margin_y = this.options.widget_margins[1]; + var max_size_x = this.resize_max_size_x; + var min_size_x = this.resize_min_size_x; + var max_size_y = this.resize_max_size_y; + var min_size_y = this.resize_min_size_y; + var autogrow = this.options.max_cols === Infinity; + var width; + + var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); + var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); + + var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); + var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); + + // Max number of cols this widget can be in width + var max_cols = Math.floor((this.container_width / this.min_widget_width) - this.resize_initial_col + 1); + + var limit_width = (max_cols * this.min_widget_width) + ((max_cols - 1) * margin_x); + + size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); + size_x = Math.min(max_cols, size_x); + width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x ); + var max_width = Math.min(width, limit_width); + var min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x); + + size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); + var max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y); + var min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y); + + if (this.resize_dir.right) { + size_y = this.resize_initial_sizey; + } else if (this.resize_dir.bottom) { + size_x = this.resize_initial_sizex; + } + + if (autogrow) { + var last_widget_col = this.resize_initial_col + size_x - 1; + if (autogrow && this.resize_initial_last_col <= last_widget_col) { + this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); + + if (this.cols < last_widget_col) { + this.add_faux_cols(last_widget_col - this.cols); + } + } + } + + var css_props = {}; + !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( + this.resize_initial_width + rel_x, max_width), min_width)); + !this.resize_dir.right && (css_props.height = Math.max(Math.min( + this.resize_initial_height + rel_y, max_height), min_height)); + + this.$resized_widget.css(css_props); + + if (size_x !== this.resize_last_sizex || + size_y !== this.resize_last_sizey) { + + this.resize_widget(this.$resized_widget, size_x, size_y, false); + this.set_dom_grid_width(this.cols); + + this.$resize_preview_holder.css({ + 'width': '', + 'height': '' + }).attr({ + 'data-row': this.$resized_widget.attr('data-row'), + 'data-sizex': size_x, + 'data-sizey': size_y + }); + } + + if (this.options.resize.resize) { + this.options.resize.resize.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resize'); + + this.resize_last_sizex = size_x; + this.resize_last_sizey = size_y; + }; + + + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function (start_callback, stop_callback) { + if (!this.colliders_data.length) { + return this; + } + var cols = this.get_targeted_columns( + this.colliders_data[0].el.data.col); + + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for (i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for (i = 0; i < last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; + }; + + + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} end_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function (start_callback, end_callback) { + if (!this.colliders_data.length) { + return this; + } + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; + + for (i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } + + for (i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; + }; + + + /** + * Sets the current position of the player + * + * @param {Number} col + * @param {Number} row + * @param {Boolean} no_player + * @method set_player + * @return {object} + */ + fn.set_player = function (col, row, no_player) { + var self = this; + var swap = false; + if (!no_player) { + this.empty_cells_player_occupies(); + } + var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; + var to_col = cell.col; + var to_row = cell.row || row; + + this.player_grid_data = { + col: to_col, + row: to_row, + size_y: this.player_grid_data.size_y, + size_x: this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + + //Added placeholder for more advanced movement. + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped(this.player_grid_data); + + var player_size_y = this.player_grid_data.size_y; + var player_size_x = this.player_grid_data.size_x; + var placeholder_cells = this.cells_occupied_by_placeholder; + var $gr = this; + + + //Queue Swaps + $overlapped_widgets.each($.proxy(function (i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + var outside_col = placeholder_cells.cols[0] + player_size_x - 1; + var outside_row = placeholder_cells.rows[0] + player_size_y - 1; + if ($w.hasClass($gr.options.static_class)) { + //next iteration + return true; + } + if ($gr.options.collision.wait_for_mouseup && $gr.drag_api.is_dragging){ + //skip the swap and just 'move' the place holder + $gr.placeholder_grid_data.col = to_col; + $gr.placeholder_grid_data.row = to_row; + + $gr.cells_occupied_by_placeholder = $gr.get_cells_occupied( + $gr.placeholder_grid_data); + + $gr.$preview_holder.attr({ + 'data-row': to_row, + 'data-col': to_col + }); + } else if (wgd.size_x <= player_size_x && wgd.size_y <= player_size_y) { + if (!$gr.is_swap_occupied(placeholder_cells.cols[0], wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], wgd.row) && !$gr.is_in_queue(placeholder_cells.cols[0], wgd.row, $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], wgd.row, $w); + } + else if (!$gr.is_swap_occupied(outside_col, wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(outside_col, wgd.row) && !$gr.is_in_queue(outside_col, wgd.row, $w)) { + swap = $gr.queue_widget(outside_col, wgd.row, $w); + } + else if (!$gr.is_swap_occupied(wgd.col, placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, placeholder_cells.rows[0]) && !$gr.is_in_queue(wgd.col, placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(wgd.col, placeholder_cells.rows[0], $w); + } + else if (!$gr.is_swap_occupied(wgd.col, outside_row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, outside_row) && !$gr.is_in_queue(wgd.col, outside_row, $w)) { + swap = $gr.queue_widget(wgd.col, outside_row, $w); + } + else if (!$gr.is_swap_occupied(placeholder_cells.cols[0], placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], placeholder_cells.rows[0]) && !$gr.is_in_queue(placeholder_cells.cols[0], placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], placeholder_cells.rows[0], $w); + } else { + //in one last attempt we check for any other empty spaces + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = placeholder_cells.cols[0] + c; + var rowc = placeholder_cells.rows[0] + r; + if (!$gr.is_swap_occupied(colc, rowc, wgd.size_x, wgd.size_y) && !$gr.is_player_in(colc, rowc) && !$gr.is_in_queue(colc, rowc, $w)) { + swap = $gr.queue_widget(colc, rowc, $w); + c = player_size_x; + break; + } + } + } + + } + } else if ($gr.options.shift_larger_widgets_down && !swap) { + $overlapped_widgets.each($.proxy(function (i, w) { + var $w = $(w); + + if ($gr.can_go_down($w) && $w.coords().grid.row === $gr.player_grid_data.row) { + $gr.move_widget_down($w, $gr.player_grid_data.size_y); + $gr.set_placeholder(to_col, to_row); + } + })); + } + + $gr.clean_up_changed(); + })); + + //Move queued widgets + if (swap && this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + for (var key in this.w_queue) { + var _col = parseInt(key.split('_')[0]); + var _row = parseInt(key.split('_')[1]); + if (this.w_queue[key] !== 'full') { + this.new_move_widget_to(this.w_queue[key], _col, _row); + } + } + this.set_placeholder(to_col, to_row); + } + + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + if (this.options.shift_widgets_up) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + } + if (this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + this.set_placeholder(to_col, to_row); + } + } + + this.w_queue = {}; + + return { + col: to_col, + row: to_row + }; + }; + + + fn.is_swap_occupied = function (col, row, w_size_x, w_size_y) { + var occupied = false; + for (var c = 0; c < w_size_x; c++) { + for (var r = 0; r < w_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + '_' + rowc; + if (this.is_occupied(colc, rowc)) { + occupied = true; + } else if (key in this.w_queue) { + if (this.w_queue[key] === 'full') { + occupied = true; + continue; + } + var $tw = this.w_queue[key]; + var tgd = $tw.coords().grid; + //remove queued items if no longer under player. + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + } + } + if (rowc > parseInt(this.options.max_rows)) { + occupied = true; + } + if (colc > parseInt(this.options.max_cols)) { + occupied = true; + } + if (this.is_player_in(colc, rowc)) { + occupied = true; + } + } + } + + return occupied; + }; + + fn.can_placeholder_be_set = function (col, row, player_size_x, player_size_y) { + var can_set = true; + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var $tw = this.is_widget(colc, rowc); + //if this space is occupied and not queued for move. + if (rowc > parseInt(this.options.max_rows)) { + can_set = false; + } + if (colc > parseInt(this.options.max_cols)) { + can_set = false; + } + if (this.is_occupied(colc, rowc) && !this.is_widget_queued_and_can_move($tw)) { + can_set = false; + } + } + } + return can_set; + }; + + fn.queue_widget = function (col, row, $widget) { + var $w = $widget; + var wgd = $w.coords().grid; + var primary_key = col + '_' + row; + if (primary_key in this.w_queue) { + return false; + } + + this.w_queue[primary_key] = $w; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + '_' + rowc; + if (key === primary_key) { + continue; + } + this.w_queue[key] = 'full'; + } + } + + return true; + }; + + fn.is_widget_queued_and_can_move = function ($widget) { + var queued = false; + if ($widget === false) { + return false; + } + + for (var key in this.w_queue) { + if (this.w_queue[key] === 'full') { + continue; + } + if (this.w_queue[key].attr('data-col') === $widget.attr('data-col') && this.w_queue[key].attr('data-row') === $widget.attr('data-row')) { + queued = true; + //test whole space + var $w = this.w_queue[key]; + var dcol = parseInt(key.split('_')[0]); + var drow = parseInt(key.split('_')[1]); + var wgd = $w.coords().grid; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = dcol + c; + var rowc = drow + r; + if (this.is_player_in(colc, rowc)) { + queued = false; + } + + } + } + + } + } + + return queued; + }; + + fn.is_in_queue = function (col, row, $widget) { + var queued = false; + var key = col + '_' + row; + + if ((key in this.w_queue)) { + if (this.w_queue[key] === 'full') { + queued = true; + } else { + var $tw = this.w_queue[key]; + var tgd = $tw.coords().grid; + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + queued = false; + } else if (this.w_queue[key].attr('data-col') === $widget.attr('data-col') && this.w_queue[key].attr('data-row') === $widget.attr('data-row')) { + delete this.w_queue[key]; + queued = false; + } else { + queued = true; + } + } + } + + return queued; + }; + + + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {jQuery} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {object} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function ($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; + + $widgets.each($.proxy(function (i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + } else { + wgd_can_not_go_up.push(wgd); + } + }, this)); + + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + + return { + can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), + can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) + }; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + + * Depreciated. + * + * @method manage_movements + * @param {jQuery} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function ($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function (i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up( + this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + if (this.can_go_down($w)) { + console.log('In Move Down!'); + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + } + }, this)); + + return this; + }; + + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function (col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { + return false; + } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; + + + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function (col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function (col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function (col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; + + + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function (col, row) { + if (typeof this.gridmap[col] !== 'undefined') { + if (typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false ) { return true; } return false; } return true; - }; - - - /** - * Determines if the cell represented by col and row params is occupied. - * - * @method is_occupied - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_occupied = function(col, row) { - if (!this.gridmap[col]) { - return false; - } - - if (this.gridmap[col][row]) { - return true; - } - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row params. - * - * @method is_widget - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean|HTMLElement} Returns false if there is no widget, - * else returns the jQuery HTMLElement - */ - fn.is_widget = function(col, row) { - var cell = this.gridmap[col]; - if (!cell) { - return false; - } - - cell = cell[row]; - - if (cell) { - return cell; - } - - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row - * params and if this is under the widget that is being dragged. - * - * @method is_widget_under_player - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_widget_under_player = function(col, row) { - if (this.is_widget(col, row)) { - return this.is_player_in(col, row); - } - return false; - }; - - - /** - * Get widgets overlapping with the player or with the object passed - * representing the grid cells. - * - * @method get_widgets_under_player - * @return {HTMLElement} Returns a jQuery collection of HTMLElements - */ - fn.get_widgets_under_player = function(cells) { - cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); - var $widgets = $([]); - - $.each(cells.cols, $.proxy(function(i, col) { - $.each(cells.rows, $.proxy(function(i, row) { - if(this.is_widget(col, row)) { - $widgets = $widgets.add(this.gridmap[col][row]); - } - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * Put placeholder at the row and column specified. - * - * @method set_placeholder - * @param {Number} col The column to which we want to move the - * placeholder. - * @param {Number} row The row to which we want to move the - * placeholder. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_placeholder = function(col, row) { - var phgd = $.extend({}, this.placeholder_grid_data); - var $nexts = this.widgets_below({ - col: phgd.col, - row: phgd.row, - size_y: phgd.size_y, - size_x: phgd.size_x - }); - - // Prevents widgets go out of the grid - var right_col = (col + phgd.size_x - 1); - if (right_col > this.cols) { - col = col - (right_col - col); - } - - var moved_down = this.placeholder_grid_data.row < row; - var changed_column = this.placeholder_grid_data.col !== col; - - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.$preview_holder.attr({ - 'data-row' : row, - 'data-col' : col - }); - - if (moved_down || changed_column) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( - $(widget), this.placeholder_grid_data.col - col + phgd.size_y); - }, this)); - } - - var $widgets_under_ph = this.get_widgets_under_player( - this.cells_occupied_by_placeholder); - - if ($widgets_under_ph.length) { - $widgets_under_ph.each($.proxy(function(i, widget) { - var $w = $(widget); - this.move_widget_down( - $w, row + phgd.size_y - $w.data('coords').grid.row); - }, this)); - } - - }; - - - /** - * Determines whether the player can move to a position above. - * - * @method can_go_player_up - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @return {Number|Boolean} If the player can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_player_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - var $widgets_under_player = this.get_widgets_under_player(); - - /* generate an array with columns as index and array with upper rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - var r = p_bottom_row + 1; - upper_rows[tcol] = []; - - while (--r > 0) { - if (this.is_empty(tcol, r) || this.is_player(tcol, r) || - this.is_widget(tcol, r) && - grid_col[r].is($widgets_under_player) - ) { - upper_rows[tcol].push(r); - min_row = r < min_row ? r : min_row; - } else { - break; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(function(a, b) { - return a - b; - }); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Determines whether a widget can move to a position above. - * - * @method can_go_widget_up - * @param {Object} widget_grid_data The actual grid coords object of the - * widget we want to check. - * @return {Number|Boolean} If the widget can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_widget_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - - /* generate an array with columns as index and array with topmost rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - upper_rows[tcol] = []; - - var r = p_bottom_row + 1; - // iterate over each row - while (--r > 0) { - if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { - if (!grid_col[r].is(widget_grid_data.el)) { - break; - } - } - - if (!this.is_player(tcol, r) && - !this.is_placeholder_in(tcol, r) && - !this.is_player_in(tcol, r)) { - upper_rows[tcol].push(r); - } - - if (r < min_row) { - min_row = r; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(function(a, b) { - return a - b; - }); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Search a valid row for the widget represented by `widget_grid_data' in - * the `upper_rows` array. Iteration starts from row specified in `min_row`. - * - * @method get_valid_rows - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @param {Array} upper_rows An array with columns as index and arrays - * of valid rows as values. - * @param {Number} min_row The upper row from which the iteration will start. - * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` - * for the widget in question. - */ - fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { - var p_top_row = widget_grid_data.row; - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var size_y = widget_grid_data.size_y; - var r = min_row - 1; - var valid_rows = []; - - while (++r <= p_bottom_row ) { - var common = true; - $.each(upper_rows, function(col, rows) { - if ($.isArray(rows) && $.inArray(r, rows) === -1) { - common = false; - } - }); - - if (common === true) { - valid_rows.push(r); - if (valid_rows.length === size_y) { - break; - } - } - } - - var new_row = false; - if (size_y === 1) { - if (valid_rows[0] !== p_top_row) { - new_row = valid_rows[0] || false; - } - } else { - if (valid_rows[0] !== p_top_row) { - new_row = this.get_consecutive_numbers_index( - valid_rows, size_y); - } - } - - return new_row; - }; - - - fn.get_consecutive_numbers_index = function(arr, size_y) { - var max = arr.length; - var result = []; - var first = true; - var prev = -1; // or null? - - for (var i=0; i < max; i++) { - if (first || arr[i] === prev + 1) { - result.push(i); - if (result.length === size_y) { - break; - } - first = false; - } else { - result = []; - first = true; - } - - prev = arr[i]; - } - - return result.length >= size_y ? arr[result[0]] : false; - }; - - - /** - * Get widgets overlapping with the player. - * - * @method get_widgets_overlapped - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.get_widgets_overlapped = function() { - var $w; - var $widgets = $([]); - var used = []; - var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); - rows_from_bottom.reverse(); - - $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { - $.each(rows_from_bottom, $.proxy(function(i, row) { - // if there is a widget in the player position - if (!this.gridmap[col]) { return true; } //next iteration - var $w = this.gridmap[col][row]; - if (this.is_occupied(col, row) && !this.is_player($w) && - $.inArray($w, used) === -1 - ) { - $widgets = $widgets.add($w); - used.push($w); - } - - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * This callback is executed when the player begins to collide with a column. - * - * @method on_start_overlapping_column - * @param {Number} col The collided column. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_column = function(col) { - this.set_player(col, false); - }; - - - /** - * A callback executed when the player begins to collide with a row. - * - * @method on_start_overlapping_row - * @param {Number} row The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_row = function(row) { - this.set_player(false, row); - }; - - - /** - * A callback executed when the the player ends to collide with a column. - * - * @method on_stop_overlapping_column - * @param {Number} col The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_column = function(col) { - this.set_player(col, false); - - var self = this; - this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], - function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - }; - - - /** - * This callback is executed when the player ends to collide with a row. - * - * @method on_stop_overlapping_row - * @param {Number} row The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_row = function(row) { - this.set_player(false, row); - - var self = this; - var cols = this.cells_occupied_by_player.cols; - for (var c = 0, cl = cols.length; c < cl; c++) { - this.for_each_widget_below(cols[c], row, function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - } - }; - - - /** - * Move a widget to a specific row. The cell or cells must be empty. - * If the widget has widgets below, all of these widgets will be moved also - * if they can. - * - * @method move_widget_to - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the - * widget is going to be moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_to = function($widget, row) { - var self = this; - var widget_grid_data = $widget.coords().grid; - var diff = row - widget_grid_data.row; - var $next_widgets = this.widgets_below($widget); - - var can_move_to_new_cell = this.can_move_to( - widget_grid_data, widget_grid_data.col, row, $widget); - - if (can_move_to_new_cell === false) { - return false; - } - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', row); - this.$changed = this.$changed.add($widget); - - - $next_widgets.each(function(i, widget) { - var $w = $(widget); - var wgd = $w.coords().grid; - var can_go_up = self.can_go_widget_up(wgd); - if (can_go_up && can_go_up !== wgd.row) { - self.move_widget_to($w, can_go_up); - } - }); - - return this; - }; - - - /** - * Move up the specified widget and all below it. - * - * @method move_widget_up - * @param {HTMLElement} $widget The widget you want to move. - * @param {Number} [y_units] The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_up = function($widget, y_units) { - var el_grid_data = $widget.coords().grid; - var actual_row = el_grid_data.row; - var moved = []; - var can_go_up = true; - y_units || (y_units = 1); - - if (!this.can_go_up($widget)) { return false; } //break; - - this.for_each_column_occupied(el_grid_data, function(col) { - // can_go_up - if ($.inArray($widget, moved) === -1) { - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row - y_units; - next_row = this.can_go_up_to_row( - widget_grid_data, col, next_row); - - if (!next_row) { - return true; - } - - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = next_row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); - - moved.push($widget); - - $next_widgets.each($.proxy(function(i, widget) { - this.move_widget_up($(widget), y_units); - }, this)); - } - }); - - }; - - - /** - * Move down the specified widget and all below it. - * - * @method move_widget_down - * @param {jQuery} $widget The jQuery object representing the widget - * you want to move. - * @param {Number} y_units The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_down = function($widget, y_units) { - var el_grid_data, actual_row, moved, y_diff; - - if (y_units <= 0) { return false; } - - el_grid_data = $widget.coords().grid; - actual_row = el_grid_data.row; - moved = []; - y_diff = y_units; - - if (!$widget) { return false; } - - if ($.inArray($widget, moved) === -1) { - - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row + y_units; - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - - $next_widgets.each($.proxy(function(i, widget) { - var $w = $(widget); - var wd = $w.coords().grid; - var tmp_y = this.displacement_diff( - wd, widget_grid_data, y_diff); - - if (tmp_y > 0) { - this.move_widget_down($w, tmp_y); - } - }, this)); - - widget_grid_data.row = next_row; - this.update_widget_position(widget_grid_data, $widget); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); - - moved.push($widget); - } - }; - - - /** - * Check if the widget can move to the specified row, else returns the - * upper row possible. - * - * @method can_go_up_to_row - * @param {Number} widget_grid_data The current grid coords object of the - * widget. - * @param {Number} col The target column. - * @param {Number} row The target row. - * @return {Boolean|Number} Returns the row number if the widget can move - * to the target position, else returns false. - */ - fn.can_go_up_to_row = function(widget_grid_data, col, row) { - var ga = this.gridmap; - var result = true; - var urc = []; // upper_rows_in_columns - var actual_row = widget_grid_data.row; - var r; - - /* generate an array with columns as index and array with - * upper rows empty in the column */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = ga[tcol]; - urc[tcol] = []; - - r = actual_row; - while (r--) { - if (this.is_empty(tcol, r) && - !this.is_placeholder_in(tcol, r) - ) { - urc[tcol].push(r); - } else { - break; - } - } - - if (!urc[tcol].length) { - result = false; - return true; - } - - }); - - if (!result) { return false; } - - /* get common rows starting from upper position in all the columns - * that widget occupies */ - r = row; - for (r = 1; r < actual_row; r++) { - var common = true; - - for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { - if (urc[uc] && $.inArray(r, urc[uc]) === -1) { - common = false; - } - } - - if (common === true) { - result = r; - break; - } - } - - return result; - }; - - - fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { - var actual_row = widget_grid_data.row; - var diffs = []; - var parent_max_y = parent_bgd.row + parent_bgd.size_y; - - this.for_each_column_occupied(widget_grid_data, function(col) { - var temp_y_units = 0; - - for (var r = parent_max_y; r < actual_row; r++) { - if (this.is_empty(col, r)) { - temp_y_units = temp_y_units + 1; - } - } - - diffs.push(temp_y_units); - }); - - var max_diff = Math.max.apply(Math, diffs); - y_units = (y_units - max_diff); - - return y_units > 0 ? y_units : 0; - }; - - - /** - * Get widgets below a widget. - * - * @method widgets_below - * @param {HTMLElement} $el The jQuery wrapped HTMLElement. - * @return {jQuery} A jQuery collection of HTMLElements. - */ - fn.widgets_below = function($el) { - var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; - var self = this; - var ga = this.gridmap; - var next_row = el_grid_data.row + el_grid_data.size_y - 1; - var $nexts = $([]); - - this.for_each_column_occupied(el_grid_data, function(col) { - self.for_each_widget_below(col, next_row, function(tcol, trow) { - if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { - $nexts = $nexts.add(this); - return true; // break - } - }); - }); - - return Gridster.sort_by_row_asc($nexts); - }; - - - /** - * Update the array of mapped positions with the new player position. - * - * @method set_cells_player_occupies - * @param {Number} col The new player col. - * @param {Number} col The new player row. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_cells_player_occupies = function(col, row) { - this.remove_from_gridmap(this.placeholder_grid_data); - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - this.add_to_gridmap(this.placeholder_grid_data, this.$player); - return this; - }; - - - /** - * Remove from the array of mapped positions the reference to the player. - * - * @method empty_cells_player_occupies - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells_player_occupies = function() { - this.remove_from_gridmap(this.placeholder_grid_data); - return this; - }; - - - fn.can_go_up = function($el) { - var el_grid_data = $el.coords().grid; - var initial_row = el_grid_data.row; - var prev_row = initial_row - 1; - var ga = this.gridmap; - var upper_rows_by_column = []; - - var result = true; - if (initial_row === 1) { return false; } - - this.for_each_column_occupied(el_grid_data, function(col) { - var $w = this.is_widget(col, prev_row); - - if (this.is_occupied(col, prev_row) || - this.is_player(col, prev_row) || - this.is_placeholder_in(col, prev_row) || - this.is_player_in(col, prev_row) - ) { - result = false; - return true; //break - } - }); - - return result; - }; - - - /** - * Check if it's possible to move a widget to a specific col/row. It takes - * into account the dimensions (`size_y` and `size_x` attrs. of the grid - * coords object) the widget occupies. - * - * @method can_move_to - * @param {Object} widget_grid_data The grid coords object that represents - * the widget. - * @param {Object} col The col to check. - * @param {Object} row The row to check. - * @param {Number} [max_row] The max row allowed. - * @return {Boolean} Returns true if all cells are empty, else return false. - */ - fn.can_move_to = function(widget_grid_data, col, row, max_row) { - var ga = this.gridmap; - var $w = widget_grid_data.el; - var future_wd = { - size_y: widget_grid_data.size_y, - size_x: widget_grid_data.size_x, - col: col, - row: row - }; - var result = true; - - //Prevents widgets go out of the grid - var right_col = col + widget_grid_data.size_x - 1; - if (right_col > this.cols) { - return false; - } - - if (max_row && max_row < row + widget_grid_data.size_y - 1) { - return false; - } - - this.for_each_cell_occupied(future_wd, function(tcol, trow) { - var $tw = this.is_widget(tcol, trow); - if ($tw && (!widget_grid_data.el || $tw.is($w))) { - result = false; - } - }); - - return result; - }; - - - /** - * Given the leftmost column returns all columns that are overlapping - * with the player. - * - * @method get_targeted_columns - * @param {Number} [from_col] The leftmost column. - * @return {Array} Returns an array with column numbers. - */ - fn.get_targeted_columns = function(from_col) { - var max = (from_col || this.player_grid_data.col) + - (this.player_grid_data.size_x - 1); - var cols = []; - for (var col = from_col; col <= max; col++) { - cols.push(col); - } - return cols; - }; - - - /** - * Given the upper row returns all rows that are overlapping with the player. - * - * @method get_targeted_rows - * @param {Number} [from_row] The upper row. - * @return {Array} Returns an array with row numbers. - */ - fn.get_targeted_rows = function(from_row) { - var max = (from_row || this.player_grid_data.row) + - (this.player_grid_data.size_y - 1); - var rows = []; - for (var row = from_row; row <= max; row++) { - rows.push(row); - } - return rows; - }; - - /** - * Get all columns and rows that a widget occupies. - * - * @method get_cells_occupied - * @param {Object} el_grid_data The grid coords object of the widget. - * @return {Object} Returns an object like `{ cols: [], rows: []}`. - */ - fn.get_cells_occupied = function(el_grid_data) { - var cells = { cols: [], rows: []}; - var i; - if (arguments[1] instanceof $) { - el_grid_data = arguments[1].coords().grid; - } - - for (i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - cells.cols.push(col); - } - - for (i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - cells.rows.push(row); - } - - return cells; - }; - - - /** - * Iterate over the cells occupied by a widget executing a function for - * each one. - * - * @method for_each_cell_occupied - * @param {Object} el_grid_data The grid coords object that represents the - * widget. - * @param {Function} callback The function to execute on each column - * iteration. Column and row are passed as arguments. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_cell_occupied = function(grid_data, callback) { - this.for_each_column_occupied(grid_data, function(col) { - this.for_each_row_occupied(grid_data, function(row) { - callback.call(this, col, row); - }); - }); - return this; - }; - - - /** - * Iterate over the columns occupied by a widget executing a function for - * each one. - * - * @method for_each_column_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The column number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_column_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - callback.call(this, col, el_grid_data); - } - }; - - - /** - * Iterate over the rows occupied by a widget executing a function for - * each one. - * - * @method for_each_row_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The row number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_row_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - callback.call(this, row, el_grid_data); - } - }; - - - - fn._traversing_widgets = function(type, direction, col, row, callback) { - var ga = this.gridmap; - if (!ga[col]) { return; } - - var cr, max; - var action = type + '/' + direction; - if (arguments[2] instanceof $) { - var el_grid_data = arguments[2].coords().grid; - col = el_grid_data.col; - row = el_grid_data.row; - callback = arguments[3]; - } - var matched = []; - var trow = row; - - - var methods = { - 'for_each/above': function() { - while (trow--) { - if (trow > 0 && this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } - } - } - }, - 'for_each/below': function() { - for (trow = row + 1, max = ga[col].length; trow < max; trow++) { - if (this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } - } - } - } - }; - - if (methods[action]) { - methods[action].call(this); - } - }; - - - /** - * Iterate over each widget above the column and row specified. - * - * @method for_each_widget_above - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery - * wrapped HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_above = function(col, row, callback) { - this._traversing_widgets('for_each', 'above', col, row, callback); - return this; - }; - - - /** - * Iterate over each widget below the column and row specified. - * - * @method for_each_widget_below - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery wrapped - * HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_below = function(col, row, callback) { - this._traversing_widgets('for_each', 'below', col, row, callback); - return this; - }; - - - /** - * Returns the highest occupied cell in the grid. - * - * @method get_highest_occupied_cell - * @return {Object} Returns an object with `col` and `row` numbers. - */ - fn.get_highest_occupied_cell = function() { - var r; - var gm = this.gridmap; - var rl = gm[1].length; - var rows = [], cols = []; - var row_in_col = []; - for (var c = gm.length - 1; c >= 1; c--) { - for (r = rl - 1; r >= 1; r--) { - if (this.is_widget(c, r)) { - rows.push(r); - cols.push(c); - break; - } - } - } - - return { - col: Math.max.apply(Math, cols), - row: Math.max.apply(Math, rows) - }; - }; - - - fn.get_widgets_from = function(col, row) { - var ga = this.gridmap; - var $widgets = $(); - - if (col) { - $widgets = $widgets.add( - this.$widgets.filter(function() { - var tcol = $(this).attr('data-col'); - return (tcol === col || tcol > col); - }) - ); - } - - if (row) { - $widgets = $widgets.add( - this.$widgets.filter(function() { - var trow = $(this).attr('data-row'); - return (trow === row || trow > row); - }) - ); - } - - return $widgets; - }; - - - /** - * Set the current height of the parent grid. - * - * @method set_dom_grid_height - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_height = function(height) { - if (typeof height === 'undefined') { - var r = this.get_highest_occupied_cell().row; - height = r * this.min_widget_height; - } - - this.container_height = height; - this.$el.css('height', this.container_height); - return this; - }; - - /** - * Set the current width of the parent grid. - * - * @method set_dom_grid_width - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_width = function(cols) { - if (typeof cols === 'undefined') { - cols = this.get_highest_occupied_cell().col; - } - - var max_cols = (this.options.autogrow_cols ? this.options.max_cols : - this.cols); - - cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); - this.container_width = cols * this.min_widget_width; - this.$el.css('width', this.container_width); - return this; - }; - - - /** - * It generates the neccessary styles to position the widgets. - * - * @method generate_stylesheet - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_stylesheet = function(opts) { - var styles = ''; - var max_size_x = this.options.max_size_x || this.cols; - var max_rows = 0; - var max_cols = 0; - var i; - var rules; - - opts || (opts = {}); - opts.cols || (opts.cols = this.cols); - opts.rows || (opts.rows = this.rows); - opts.namespace || (opts.namespace = this.options.namespace); - opts.widget_base_dimensions || - (opts.widget_base_dimensions = this.options.widget_base_dimensions); - opts.widget_margins || - (opts.widget_margins = this.options.widget_margins); - opts.min_widget_width = (opts.widget_margins[0] * 2) + - opts.widget_base_dimensions[0]; - opts.min_widget_height = (opts.widget_margins[1] * 2) + - opts.widget_base_dimensions[1]; - - // don't duplicate stylesheets for the same configuration - var serialized_opts = $.param(opts); - if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { - return false; - } - - this.generated_stylesheets.push(serialized_opts); - Gridster.generated_stylesheets.push(serialized_opts); - - /* generate CSS styles for cols */ - for (i = opts.cols; i >= 0; i--) { - styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + - ((i * opts.widget_base_dimensions[0]) + - (i * opts.widget_margins[0]) + - ((i + 1) * opts.widget_margins[0])) + 'px; }\n'); - } - - /* generate CSS styles for rows */ - for (i = opts.rows; i >= 0; i--) { - styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + - ((i * opts.widget_base_dimensions[1]) + - (i * opts.widget_margins[1]) + - ((i + 1) * opts.widget_margins[1]) ) + 'px; }\n'); - } - - for (var y = 1; y <= opts.rows; y++) { - styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + - (y * opts.widget_base_dimensions[1] + - (y - 1) * (opts.widget_margins[1] * 2)) + 'px; }\n'); - } - - for (var x = 1; x <= max_size_x; x++) { - styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + - (x * opts.widget_base_dimensions[0] + - (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n'); - } - - this.remove_style_tags(); - - return this.add_style_tag(styles); - }; - - - /** - * Injects the given CSS as string to the head of the document. - * - * @method add_style_tag - * @param {String} css The styles to apply. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_style_tag = function(css) { - var d = document; - var tag = d.createElement('style'); - - d.getElementsByTagName('head')[0].appendChild(tag); - tag.setAttribute('type', 'text/css'); - - if (tag.styleSheet) { - tag.styleSheet.cssText = css; - } else { - tag.appendChild(document.createTextNode(css)); - } - - this.$style_tags = this.$style_tags.add(tag); - - return this; - }; - - - /** - * Remove the style tag with the associated id from the head of the document - * - * @method remove_style_tag - * @return {Object} Returns the instance of the Gridster class. - */ - fn.remove_style_tags = function() { - var all_styles = Gridster.generated_stylesheets; - var ins_styles = this.generated_stylesheets; - - this.$style_tags.remove(); - - Gridster.generated_stylesheets = $.map(all_styles, function(s) { - if ($.inArray(s, ins_styles) === -1) { return s; } - }); - }; - - - /** - * Generates a faux grid to collide with it when a widget is dragged and - * detect row or column that we want to go. - * - * @method generate_faux_grid - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_faux_grid = function(rows, cols) { - this.faux_grid = []; - this.gridmap = []; - var col; - var row; - for (col = cols; col > 0; col--) { - this.gridmap[col] = []; - for (row = rows; row > 0; row--) { - this.add_faux_cell(row, col); - } - } - return this; - }; - - - /** - * Add cell to the faux grid. - * - * @method add_faux_cell - * @param {Number} row The row for the new faux cell. - * @param {Number} col The col for the new faux cell. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cell = function(row, col) { - var coords = $({ - left: this.baseX + ((col - 1) * this.min_widget_width), - top: this.baseY + (row -1) * this.min_widget_height, - width: this.min_widget_width, - height: this.min_widget_height, - col: col, - row: row, - original_col: col, - original_row: row - }).coords(); - - if (!$.isArray(this.gridmap[col])) { - this.gridmap[col] = []; - } - - this.gridmap[col][row] = false; - this.faux_grid.push(coords); - - return this; - }; - - - /** - * Add rows to the faux grid. - * - * @method add_faux_rows - * @param {Number} rows The number of rows you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_rows = function(rows) { - var actual_rows = this.rows; - var max_rows = actual_rows + (rows || 1); - - for (var r = max_rows; r > actual_rows; r--) { - for (var c = this.cols; c >= 1; c--) { - this.add_faux_cell(r, c); - } - } - - this.rows = max_rows; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - /** - * Add cols to the faux grid. - * - * @method add_faux_cols - * @param {Number} cols The number of cols you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cols = function(cols) { - var actual_cols = this.cols; - var max_cols = actual_cols + (cols || 1); - max_cols = Math.min(max_cols, this.options.max_cols); - - for (var c = actual_cols + 1; c <= max_cols; c++) { - for (var r = this.rows; r >= 1; r--) { - this.add_faux_cell(r, c); - } - } - - this.cols = max_cols; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - - /** - * Recalculates the offsets for the faux grid. You need to use it when - * the browser is resized. - * - * @method recalculate_faux_grid - * @return {Object} Returns the instance of the Gridster class. - */ - fn.recalculate_faux_grid = function() { - var aw = this.$wrapper.width(); - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; - - $.each(this.faux_grid, $.proxy(function(i, coords) { - this.faux_grid[i] = coords.update({ - left: this.baseX + (coords.data.col -1) * this.min_widget_width, - top: this.baseY + (coords.data.row -1) * this.min_widget_height - }); - }, this)); - - return this; - }; - - - /** - * Get all widgets in the DOM and register them. - * - * @method get_widgets_from_DOM - * @return {Object} Returns the instance of the Gridster class. - */ - fn.get_widgets_from_DOM = function() { - var widgets_coords = this.$widgets.map($.proxy(function(i, widget) { - var $w = $(widget); - return this.dom_to_coords($w); - }, this)); - - widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); - - var changes = $(widgets_coords).map($.proxy(function(i, wgd) { - return this.register_widget(wgd) || null; - }, this)); - - if (changes.length) { - this.$el.trigger('gridster:positionschanged'); - } - - return this; - }; - - - /** - * Calculate columns and rows to be set based on the configuration - * parameters, grid dimensions, etc ... - * - * @method generate_grid_and_stylesheet - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_grid_and_stylesheet = function() { - var aw = this.$wrapper.width(); - var max_cols = this.options.max_cols; - - var cols = Math.floor(aw / this.min_widget_width) + - this.options.extra_cols; - - var actual_cols = this.$widgets.map(function() { - return $(this).attr('data-col'); - }).get(); - - //needed to pass tests with phantomjs - actual_cols.length || (actual_cols = [0]); - - var min_cols = Math.max.apply(Math, actual_cols); - - this.cols = Math.max(min_cols, cols, this.options.min_cols); - - if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { - this.cols = max_cols; - } - - // get all rows that could be occupied by the current widgets - var max_rows = this.options.extra_rows; - this.$widgets.each(function(i, w) { - max_rows += (+$(w).attr('data-sizey')); - }); - - this.rows = Math.max(max_rows, this.options.min_rows); - - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; + }; + + + /** + * checks the grid to see if the desired column is a valid row in the config + * @Param {Number} col number to check + * @Param {Number} [size_y] optional number of columns in the offset + * @Return {Boolean} true if the desire column exists in the grid. + */ + fn.is_valid_col = function (col, size_x) { + //if the grid is set to autogrow all cols are valid + if (this.options.max_cols === Infinity) { + return true; + } + return this.cols >= this.calculate_highest_col(col, size_x) ; + }; + + /** + * checks the grid to see if the desired row is a valid row in the config + * @Param {Number} row number to check + * @Param {Number} [size_y] optional number of rows in the offset + * @Return {Boolean} true if the desire row exists in the grid. + */ + fn.is_valid_row = function (row, size_y){ + return this.rows >= this.calculate_highest_row(row, size_y); + }; + + /** + * extract out the logic to calculate the highest col the widget needs + * in the grid in order to fit. Based on the current row and desired size + * @param {Number} col the column number of the current postiton of the widget + * @param {Number} [size_x] veritical size of the widget + * @returns {number} highest col needed to contain the widget + */ + fn.calculate_highest_col = function (col, size_x) { + return col + (size_x || 1) - 1; + }; + + /** + * extract out the logic to calculate the highest row the widget needs + * in the grid in order to fit. Based on the current row and desired size + * @param {Number} row the row number of the current postiton of the widget + * @param {Number} [size_y] horizontal size of the widget + * @returns {number} highest row needed to contain the widget + */ + fn.calculate_highest_row = function (row, size_y){ + return row + (size_y || 1) - 1; + }; + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function (col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function (col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + /** + * Determines if widget is supposed to be static. + * WARNING: as of 0.6.6 this feature is buggy when + * used with resizable widgets, as resizing widgets + * above and below a static widgit can cause it to move. + * This feature is considered experimental at this time + * @method is_static + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true if widget exists and has static class, + * else returns false + */ + fn.is_static = function (col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + if (cell.hasClass(this.options.static_class)) { + return true; + } + } + + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function (col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + + /** + * Get widgets overlapping with the player or with the object passed + * representing the grid cells. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function (cells) { + cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function (i, col) { + $.each(cells.rows, $.proxy(function (i, row) { + if (this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function (col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + + // Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); + } + + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; + + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.$preview_holder.attr({ + 'data-row': row, + 'data-col': col + }); + + if (this.options.shift_player_up) { + if (moved_down || changed_column) { + + var $nexts = this.widgets_below({ + col: phgd.col, + row: phgd.row, + size_y: phgd.size_y, + size_x: phgd.size_x + }); + + $nexts.each($.proxy(function (i, widget) { + //Make sure widget is at it's topmost position + var $w = $(widget); + var wgd = $w.coords().grid; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + this.move_widget_to($w, can_go_widget_up); + } + + }, this)); + } + + var $widgets_under_ph = this.get_widgets_under_player( + this.cells_occupied_by_placeholder); + + if ($widgets_under_ph.length) { + $widgets_under_ph.each($.proxy(function (i, widget) { + var $w = $(widget); + this.move_widget_down( + $w, row + phgd.size_y - $w.data('coords').grid.row); + }, this)); + } + } + + }; + + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function (widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || + this.is_widget(tcol, r) && + grid_col[r].is($widgets_under_player) + ) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + } else { + break; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function (a, b) { + return a - b; + }); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function (widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + /* generate an array with columns as index and array with topmost rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + // iterate over each row + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + } + } + + if (!this.is_player(tcol, r) && !this.is_placeholder_in(tcol, r) && !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + } + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function (a, b) { + return a - b; + }); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function (widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row) { + var common = true; + /*jshint -W083 */ + $.each(upper_rows, function (col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + /*jshint +W083 */ + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + } else { + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index( + valid_rows, size_y); + } + } + + return new_row; + }; + + + fn.get_consecutive_numbers_index = function (arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; // or null? + + for (var i = 0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + } else { + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function () { + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function (i, col) { + $.each(rows_from_bottom, $.proxy(function (i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { + return true; + } //next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && + $.inArray($w, used) === -1 + ) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function (col) { + this.set_player(col, undefined , false); + }; + + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function (row) { + this.set_player(undefined, row, false); + }; + + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function (col) { + //this.set_player(col, false); + var self = this; + if (this.options.shift_larger_widgets_down) { + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], + function (tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function (row) { + //this.set_player(false, row); + var self = this; + var cols = this.cells_occupied_by_player.cols; + if (this.options.shift_larger_widgets_down) { + /*jshint -W083 */ + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function (tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + /*jshint +W083 */ + } + }; + + //Not yet part of api - DM. + fn.new_move_widget_to = function ($widget, col, row) { + var widget_grid_data = $widget.coords().grid; + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + widget_grid_data.col = col; + + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + $widget.attr('data-col', col); + this.update_widget_position(widget_grid_data, $widget); + this.$changed = this.$changed.add($widget); + + return this; + }; + + + /** + * Move a widget to a specific row and column. + * If the widget has widgets below, all of these widgets will be moved also + * + * @method move_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @param {Number} new_col the column number to be set in widget + * @param {Number} new_row the row number to be set in widget + * @param {Function} callback is called when whole process is done. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.move_widget = function ($widget, new_col, new_row, callback) { + var wgd = $widget.coords().grid; + + var new_grid_data = { + col: new_col, + row: new_row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.col, new_grid_data.row); + } + + return $widget; + }; + + + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Gridster} Returns the instance of the Gridster Class. + * @param row - row to move the widget to + */ + fn.move_widget_to = function ($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to( + widget_grid_data, widget_grid_data.col, row); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + + $next_widgets.each(function (i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Boolean} Returns if the widget moved + */ + fn.move_widget_up = function ($widget, y_units) { + if (y_units === undefined){ + return false; + } + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + y_units || (y_units = 1); + + if (!this.can_go_up($widget)) { + return false; + } //break; + + this.for_each_column_occupied(el_grid_data, function (col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row( + widget_grid_data, col, next_row); + + if (!next_row) { + return true; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + /* $next_widgets.each($.proxy(function(i, widget) { + console.log('from_within_move_widget_up'); + this.move_widget_up($(widget), y_units); + }, this)); */ + } + }); + + }; + + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {jQuery} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} y_units The number of cells that the widget has to move. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function ($widget, y_units) { + var el_grid_data, actual_row, moved, y_diff; + + if (y_units <= 0) { + return false; + } + + el_grid_data = $widget.coords().grid; + actual_row = el_grid_data.row; + moved = []; + y_diff = y_units; + + if (!$widget) { + return false; + } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function (i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff( + wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + + widget_grid_data.row = next_row; + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + } + }; + + + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function (widget_grid_data, col, row) { + var result = true; + var urc = []; // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + urc[tcol] = []; + + r = actual_row; + while (r--) { + if (this.is_empty(tcol, r) && !this.is_placeholder_in(tcol, r) + ) { + urc[tcol].push(r); + } else { + break; + } + } + + if (!urc[tcol].length) { + result = false; + return true; + } + + }); + + if (!result) { + return false; + } + + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for (r = 1; r < actual_row; r++) { + var common = true; + + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } + + if (common === true) { + result = r; + break; + } + } + + return result; + }; + + + fn.displacement_diff = function (widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; + + this.for_each_column_occupied(widget_grid_data, function (col) { + var temp_y_units = 0; + + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; + } + } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {object} $el The jQuery wrapped HTMLElement. + * @return {jQuery} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function ($el) { + var $nexts = $([]); + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + if (el_grid_data === undefined) { + //there is no grid, so we can't calculate the widgets below + return $nexts; + } + var self = this; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + + this.for_each_column_occupied(el_grid_data, function (col) { + self.for_each_widget_below(col, next_row, function (tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; // break + } + }); + }); + + return Gridster.sort_by_row_asc($nexts); + }; + + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Gridster} Returns the instance of the Gridster Class. + * @param row + */ + fn.set_cells_player_occupies = function (col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function () { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + fn.can_go_down = function ($el) { + var can_go_down = true; + var $gr = this; + + if ($el.hasClass(this.options.static_class)) { + can_go_down = false; + } + + this.widgets_below($el).each(function () { + if ($(this).hasClass($gr.options.static_class)) { + can_go_down = false; + } + }); + + return can_go_down; + }; + + + fn.can_go_up = function ($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + + var result = true; + if (initial_row === 1) { + return false; + } + + this.for_each_column_occupied(el_grid_data, function (col) { + if (this.is_occupied(col, prev_row) || + this.is_player(col, prev_row) || + this.is_placeholder_in(col, prev_row) || + this.is_player_in(col, prev_row) + ) { + result = false; + return true; //break + } + }); + + return result; + }; + + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function (widget_grid_data, col, row) { + var $w = widget_grid_data.el; + var future_wd = { + size_y: widget_grid_data.size_y, + size_x: widget_grid_data.size_x, + col: col, + row: row + }; + var result = true; + + //Prevents widgets go out of the grid, check only if the grid is not set to autogrow + if (this.options.max_cols !== Infinity) { + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + } + } + + if (this.options.max_rows < row + widget_grid_data.size_y - 1) { + return false; + } + + this.for_each_cell_occupied(future_wd, function (tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function (from_col) { + var max = (from_col || this.player_grid_data.col) + + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function (from_row) { + var max = (from_row || this.player_grid_data.row) + + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function (el_grid_data) { + var cells = {cols: [], rows: []}; + var i; + if (arguments[1] instanceof $) { + el_grid_data = arguments[1].coords().grid; + } + + for (i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for (i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * widget. + * @param grid_data + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function (grid_data, callback) { + this.for_each_column_occupied(grid_data, function (col) { + this.for_each_row_occupied(grid_data, function (row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function (el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function (el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + fn.clean_up_changed = function () { + var $gr = this; + $gr.$changed.each(function () { + if ($gr.options.shift_larger_widgets_down) { + $gr.move_widget_up($(this)); + } + }); + }; + + + /** + * Traverse a series of widgets + * @param type - currently supported 'for_each' + * @param direction - The direction to traverse. Supports 'above' and 'below' + * NOTE: the directions are based on the layout in the grid above is toward the top + * and below is toward the bottom. EG opposite direction of the row numbers + * @param col - column to traverse + * @param row - starting row in the column + * @param callback - a function that will be called for every widget found + * @private + */ + fn._traversing_widgets = function (type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { + return; + } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof $) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + + var methods = { + 'for_each/above': function () { + while (trow--) { + if (trow > 0 && this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { + break; + } + } + } + }, + 'for_each/below': function () { + for (trow = row + 1, max = ga[col].length; trow < max; trow++) { + if (this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + //break was causing problems, leaving for testing. + //if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function (col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function (col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function () { + var r; + var gm = this.gridmap; + var rl = gm[1].length; + var rows = [], cols = []; + for (var c = gm.length - 1; c >= 1; c--) { + for (r = rl - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + cols.push(c); + break; + } + } + } + + return { + col: Math.max.apply(Math, cols), + row: Math.max.apply(Math, rows) + }; + }; + + /** + * return the widgets what exist within the given range of grid cells + * @param col1 - col of upper left search + * @param row1 - row of upper left search + * @param col2 - col of lower right search + * @param row2 - row of lower right search + * @returns {*|jQuery|HTMLElement} - a collection of the cells within the range + */ + fn.get_widgets_in_range = function (col1, row1, col2, row2) { + var $widgets = $([]); + var c, r, $w, wgd; + + for (c = col2; c >= col1; c--) { + for (r = row2; r >= row1; r--) { + $w = this.is_widget(c, r); + + if ($w !== false) { + wgd = $w.data('coords').grid; + if (wgd.col >= col1 && wgd.col <= col2 && + wgd.row >= row1 && wgd.row <= row2 + ) { + $widgets = $widgets.add($w); + } + } + } + } + + return $widgets; + }; + + /** + * return any widget which is located at the given coordinates + * @param col - col to search at + * @param row - row to search at + * @returns {*} - a collection of any widgets found. + */ + fn.get_widgets_at_cell = function (col, row) { + return this.get_widgets_in_range(col, row, col, row); + }; + + /** + * gets the list of widgets in either the row or the col passed in_loop + * Not sure if this makes sense for the API or what the use case is, + * but I'm documenting it as it exists. + * @param col - a col to search for widgets from + * @param row - a row to search for widgets from + * @returns {*|jQuery|HTMLElement} - a collection of the widgets in either + * the row or the col passed in. + * + * @deprecated - if you want to search for widgets in a cell or a range + * look at get_widgets_in_range and get_widgets_at_cell + */ + fn.get_widgets_from = function (col, row) { + var $widgets = $(); + + if (col) { + $widgets = $widgets.add( + this.$widgets.filter(function () { + var tcol = parseInt($(this).attr('data-col')); + return (tcol === col || tcol > col); + }) + ); + } + + if (row) { + $widgets = $widgets.add( + this.$widgets.filter(function () { + var trow = parseInt($(this).attr('data-row')); + return (trow === row || trow > row); + }) + ); + } + + return $widgets; + }; + + + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function (height) { + if (typeof height === 'undefined') { + var r = this.get_highest_occupied_cell().row; + height = ((r + 1) * this.options.widget_margins[1]) + (r * this.min_widget_height); + } + + this.container_height = height; + this.$el.css('height', this.container_height); + return this; + }; + + /** + * Set the current width of the parent grid. + * + * @method set_dom_grid_width + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_width = function (cols) { + if (typeof cols === 'undefined') { + cols = this.get_highest_occupied_cell().col; + } + + var max_cols = (this.options.max_cols === Infinity ? this.options.max_cols : this.cols); + + cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); + this.container_width = ((cols + 1) * this.options.widget_margins[0]) + (cols * this.min_widget_width); + if (this.is_responsive()) { + this.$el.css({'min-width': '100%', 'max-width': '100%'}); + return this; //if we are responsive exit before setting the width of $el + } + this.$el.css('width', this.container_width); + + return this; + }; + + + /** + * Checks if this grid is responsive. + * autogenerate_stylesheet be true, the widget base width should be auto, and there must be a max_cols set. + * @returns {Boolean} + */ + fn.is_responsive = function () { + return this.options.autogenerate_stylesheet && this.options.widget_base_dimensions[0] === 'auto' && this.options.max_cols !== Infinity; + }; + + /** + * Generates the width of the grid columns based on the width of the window. + * @returns {number} + */ + fn.get_responsive_col_width = function () { + var cols = this.cols || this.options.max_cols; + return (this.$el[0].scrollWidth - ((cols + 1) * this.options.widget_margins[0])) / cols; + }; + + /** + * Changes the minimum width of a widget based on the width of the window and the number of cols that can + * fit in it. + * @returns {Gridster} + */ + fn.resize_responsive_layout = function () { + this.min_widget_width = this.get_responsive_col_width(); + this.generate_stylesheet(); + this.update_widgets_dimensions(); + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + return this; + }; + + /** + * Switches between collapsed widgets the span the full width when the responsive_breakpoint is triggered. + * @param collapse + * @param opts + * @returns {Gridster} + */ + fn.toggle_collapsed_grid = function (collapse, opts) { + if (collapse) { + this.$widgets.css({ + 'margin-top': opts.widget_margins[0], + 'margin-bottom': opts.widget_margins[0], + 'min-height': opts.widget_base_dimensions[1] + }); + + this.$el.addClass('collapsed'); + + if (this.resize_api) { + this.disable_resize(); + } + + if (this.drag_api) { + this.disable(); + } + } else { + this.$widgets.css({ + 'margin-top': 'auto', + 'margin-bottom': 'auto', + 'min-height': 'auto' + }); + this.$el.removeClass('collapsed'); + if (this.resize_api) { + this.enable_resize(); + } + + if (this.drag_api) { + this.enable(); + } + } + return this; + }; + + /** + * It generates the necessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {object} [opts] - set of gridster config options to generate stylessheets based on + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function (opts) { + var styles = ''; + var i; + var full_width = this.is_responsive() && this.options.responsive_breakpoint && ($(window).width() < this.options.responsive_breakpoint); + + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = this.options.namespace); + opts.widget_base_dimensions || + (opts.widget_base_dimensions = this.options.widget_base_dimensions); + + opts.widget_margins || (opts.widget_margins = this.options.widget_margins); + + if (this.is_responsive()) { + opts.widget_base_dimensions = [this.get_responsive_col_width(), opts.widget_base_dimensions[1]]; + this.toggle_collapsed_grid(full_width, opts); + } + + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + } + + this.generated_stylesheets.push(serialized_opts); + Gridster.generated_stylesheets.push(serialized_opts); + + /* generate CSS styles for cols */ + for (i = 1; i <= opts.cols + 1; i++) { + styles += (opts.namespace + ' [data-col="' + i + '"] { left:' + + (full_width ? this.options.widget_margins[0] : + ((i * opts.widget_margins[0]) + ((i - 1) * opts.widget_base_dimensions[0]))) + 'px; }\n'); + } + + /* generate CSS styles for rows */ + for (i = 1; i <= opts.rows + 1; i++) { + styles += (opts.namespace + ' [data-row="' + i + '"] { top:' + + ((i * opts.widget_margins[1]) + ((i - 1) * opts.widget_base_dimensions[1])) + 'px; }\n'); + } + + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + + (full_width ? 'auto' : ((y * opts.widget_base_dimensions[1]) + ((y - 1) * opts.widget_margins[1]))) + (full_width ? '' : 'px') + '; }\n'); + + } + + for (var x = 1; x <= opts.cols; x++) { + var colWidth = ((x * opts.widget_base_dimensions[0]) + ((x - 1) * opts.widget_margins[0])); + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + + (full_width ? (this.$wrapper.width() - this.options.widget_margins[0] * 2) : colWidth > this.$wrapper.width() ? this.$wrapper.width() : colWidth ) + 'px; }\n'); + + } + + this.remove_style_tags(); + + return this.add_style_tag(styles); + }; + + + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function (css) { + var d = document; + var cssID = 'gridster-stylesheet'; + if (this.options.namespace !== '') { + cssID = cssID + '-' + this.options.namespace; + } + + if (!document.getElementById(cssID)) { + var tag = d.createElement('style'); + tag.id = cssID; + + d.getElementsByTagName('head')[0].appendChild(tag); + tag.setAttribute('type', 'text/css'); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + } else { + tag.appendChild(document.createTextNode(css)); + } + + this.remove_style_tags(); + this.$style_tags = this.$style_tags.add(tag); + } + + return this; + }; + + + /** + * Remove the style tag with the associated id from the head of the document + * + * @method remove_style_tag + * @return {Object} Returns the instance of the Gridster class. + */ + fn.remove_style_tags = function () { + var all_styles = Gridster.generated_stylesheets; + var ins_styles = this.generated_stylesheets; + + this.$style_tags.remove(); + + Gridster.generated_stylesheets = $.map(all_styles, function (s) { + if ($.inArray(s, ins_styles) === -1) { + return s; + } + }); + }; + + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function (rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for (col = cols; col > 0; col--) { + this.gridmap[col] = []; + for (row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function (row, col) { + var coords = $({ + left: this.baseX + ((col - 1) * this.min_widget_width), + top: this.baseY + (row - 1) * this.min_widget_height, + width: this.min_widget_width, + height: this.min_widget_height, + col: col, + row: row, + original_col: col, + original_row: row + }).coords(); + + if (!$.isArray(this.gridmap[col])) { + this.gridmap[col] = []; + } + + if (typeof this.gridmap[col][row] === 'undefined') { + this.gridmap[col][row] = false; + } + this.faux_grid.push(coords); + + return this; + }; + + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function (rows) { + rows = window.parseInt(rows, 10); + + var actual_rows = this.rows; + var max_rows = actual_rows + parseInt(rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + } + } + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Add cols to the faux grid. + * + * @method add_faux_cols + * @param {Number} cols The number of cols you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cols = function (cols) { + cols = window.parseInt(cols, 10); + + var actual_cols = this.cols; + var max_cols = actual_cols + parseInt(cols || 1); + max_cols = Math.min(max_cols, this.options.max_cols); + + for (var c = actual_cols + 1; c <= max_cols; c++) { + for (var r = this.rows; r >= 1; r--) { + this.add_faux_cell(r, c); + } + } + + this.cols = max_cols; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function () { + var aw = this.$wrapper.width(); + this.baseX = ($window.width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.$wrapper.css('position') === 'relative') { + this.baseX = this.baseY = 0; + } + + $.each(this.faux_grid, $.proxy(function (i, coords) { + this.faux_grid[i] = coords.update({ + left: this.baseX + (coords.data.col - 1) * this.min_widget_width, + top: this.baseY + (coords.data.row - 1) * this.min_widget_height + }); + }, this)); + + if (this.is_responsive()) { + this.resize_responsive_layout(); + } + + if (this.options.center_widgets) { + this.center_widgets(); + } + + return this; + }; + + + /** + * Resize dimensions of widgets in grid based on given options + * + * @method resize_widget_dimensions + * @param options + * @returns {Gridster} + */ + fn.resize_widget_dimensions = function (options) { + if (options.widget_margins) { + this.options.widget_margins = options.widget_margins; + } + + if (options.widget_base_dimensions) { + this.options.widget_base_dimensions = options.widget_base_dimensions; + } + + this.$widgets.each($.proxy(function (i, widget) { + var $widget = $(widget); + this.resize_widget($widget); + }, this)); + + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + return this; + }; + + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function () { + var widgets_coords = this.$widgets.map($.proxy(function (i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + var changes = $(widgets_coords).map($.proxy(function (i, wgd) { + return this.register_widget(wgd) || null; + }, this)); + + if (changes.length) { + this.$el.trigger('gridster:positionschanged'); + } + + return this; + }; + + fn.get_num_widgets = function () { + return this.$widgets.size(); + }; + + /** + * Helper function used to set the current number of columns in the grid + * + * @param wrapper_width + */ + fn.set_num_columns = function (wrapper_width) { + + var max_cols = this.options.max_cols; + + var cols = Math.floor(wrapper_width / (this.min_widget_width + this.options.widget_margins[0])) + + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function () { + return $(this).attr('data-col'); + }).get(); + + //needed to pass tests with phantomjs + actual_cols.length || (actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + this.cols = Math.max(min_cols, cols, this.options.min_cols); + + if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { + this.cols = max_cols; + } + + if (this.drag_api) { + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + }; + + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function () { + var aw = this.$wrapper.width(); + + this.set_num_columns(aw); + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function (i, w) { + max_rows += (+$(w).attr('data-sizey')); + }); + + //this.rows = Math.max(max_rows, this.options.min_rows); + this.rows = this.options.max_rows; + + this.baseX = ($window.width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this.generate_faux_grid(this.rows, this.cols); + }; + + /** + * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks + * + * @method destroy + * @param {Boolean} remove If true, remove gridster from DOM. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.destroy = function (remove) { + this.$el.removeData('gridster'); + + // remove coords from elements + $.each(this.$widgets, function () { + $(this).removeData('coords'); + }); - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this.generate_faux_grid(this.rows, this.cols); - }; - - /** - * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks - * - * @method destroy - * @param {Boolean} remove If true, remove gridster from DOM. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.destroy = function(remove) { - this.$el.removeData('gridster'); - - // remove bound callback on window resize - $(window).unbind('.gridster'); + // remove bound callback on window resize + $window.unbind('.gridster'); - if (this.drag_api) { - this.drag_api.destroy(); - } + if (this.drag_api) { + this.drag_api.destroy(); + } + if (this.resize_api) { + this.resize_api.destroy(); + } - this.remove_style_tags(); + this.$widgets.each(function (i, el) { + $(el).coords().destroy(); + }); - remove && this.$el.remove(); + if (this.resize_api) { + this.resize_api.destroy(); + } + + this.remove_style_tags(); - return this; - }; + remove && this.$el.remove(); + + return this; + }; - //jQuery adapter - $.fn.gridster = function(options) { - return this.each(function() { - if (! $(this).data('gridster')) { - $(this).data('gridster', new Gridster( this, options )); - } - }); - }; + //jQuery adapter + $.fn.gridster = function (options) { + return this.each(function () { + var $this = $(this); + if (!$this.data('gridster')) { + $this.data('gridster', new Gridster(this, options)); + } + }); + }; - return Gridster; + return Gridster; })); ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.gridster.js')); + } + else if (typeof define === 'function' && define.amd) { define(['jquery', 'gridster'], factory); } else { root.Gridster = factory(root.$ || root.jQuery, root.Gridster); } }(this, function($, Gridster) { - + 'use strict'; var fn = Gridster.prototype; fn.widgets_in_col = function(col) { @@ -4022,32 +5133,6 @@ return false; }; - - fn.widgets_in_range = function(col1, row1, col2, row2) { - var valid_cols = []; - var valid_rows = []; - var $widgets = $([]); - var c, r, $w, wgd; - - for (c = col2; c >= col1; c--) { - for (r = row2; r >= row1; r--) { - $w = this.is_widget(c, r); - - if ($w !== false) { - wgd = $w.data('coords').grid; - if (wgd.col >= col1 && wgd.col <= col2 && - wgd.row >= row1 && wgd.row <= row2 - ) { - $widgets = $widgets.add($w); - } - } - } - } - - return $widgets; - }; - - fn.get_bottom_most_occupied_cell = function() { var row = 0; var col = 0; @@ -4096,7 +5181,7 @@ }; - fn.next_position_in_range = function(size_x, size_y, max_rows) { + fn.next_position_in_range = function(size_x, size_y) { size_x || (size_x = 1); size_y || (size_y = 1); var ga = this.gridmap; @@ -4105,12 +5190,12 @@ var rows_l; for (var c = 1; c < cols_l; c++) { - rows_l = max_rows || ga[c].length; + rows_l = this.options.max_rows || ga[c].length; for (var r = 1; r <= rows_l; r++) { var can_move_to = this.can_move_to({ size_x: size_x, size_y: size_y - }, c, r, max_rows); + }, c, r); if (can_move_to) { valid_pos.push({ @@ -4146,7 +5231,6 @@ fn.closest_to_left = function(col, row) { - var cols_l = this.gridmap.length - 1; if (!this.gridmap[col]) { return false; } for (var c = col; c >= 1; c--) { diff --git a/lib/gridster/dist/jquery.gridster.with-extras.min.js b/lib/gridster/dist/jquery.gridster.with-extras.min.js index 1ebb2b251..ed46ccb8e 100644 --- a/lib/gridster/dist/jquery.gridster.with-extras.min.js +++ b/lib/gridster/dist/jquery.gridster.with-extras.min.js @@ -1,2 +1,2 @@ -/*! gridster.js - v0.5.6 - 2014-09-25 - * http://gridster.net/ - Copyright (c) 2014 ducksboard; Licensed MIT */ (function(t,i){"function"==typeof define&&define.amd?define("gridster-coords",["jquery"],i):t.GridsterCoords=i(t.$||t.jQuery)})(this,function(t){function i(i){return i[0]&&t.isPlainObject(i[0])?this.data=i[0]:this.el=i,this.isCoords=!0,this.coords={},this.init(),this}var e=i.prototype;return e.init=function(){this.set(),this.original_coords=this.get()},e.set=function(t,i){var e=this.el;if(e&&!t&&(this.data=e.offset(),this.data.width=e.width(),this.data.height=e.height()),e&&t&&!i){var s=e.offset();this.data.top=s.top,this.data.left=s.left}var r=this.data;return r.left===void 0&&(r.left=r.x1),r.top===void 0&&(r.top=r.y1),this.coords.x1=r.left,this.coords.y1=r.top,this.coords.x2=r.left+r.width,this.coords.y2=r.top+r.height,this.coords.cx=r.left+r.width/2,this.coords.cy=r.top+r.height/2,this.coords.width=r.width,this.coords.height=r.height,this.coords.el=e||!1,this},e.update=function(i){if(!i&&!this.el)return this;if(i){var e=t.extend({},this.data,i);return this.data=e,this.set(!0,!0)}return this.set(!0),this},e.get=function(){return this.coords},e.destroy=function(){this.el.removeData("coords"),delete this.el},t.fn.coords=function(){if(this.data("coords"))return this.data("coords");var t=new i(this,arguments[0]);return this.data("coords",t),t},i}),function(t,i){"function"==typeof define&&define.amd?define("gridster-collision",["jquery","gridster-coords"],i):t.GridsterCollision=i(t.$||t.jQuery,t.GridsterCoords)}(this,function(t){function i(i,s,r){this.options=t.extend(e,r),this.$element=i,this.last_colliders=[],this.last_colliders_coords=[],this.set_colliders(s),this.init()}var e={colliders_context:document.body,overlapping_region:"C"};i.defaults=e;var s=i.prototype;return s.init=function(){this.find_collisions()},s.overlaps=function(t,i){var e=!1,s=!1;return(i.x1>=t.x1&&i.x1<=t.x2||i.x2>=t.x1&&i.x2<=t.x2||t.x1>=i.x1&&t.x2<=i.x2)&&(e=!0),(i.y1>=t.y1&&i.y1<=t.y2||i.y2>=t.y1&&i.y2<=t.y2||t.y1>=i.y1&&t.y2<=i.y2)&&(s=!0),e&&s},s.detect_overlapping_region=function(t,i){var e="",s="";return t.y1>i.cy&&t.y1i.y1&&t.y2i.cx&&t.x1i.x1&&t.x2o;o++)-1===t.inArray(r[o],i)&&e.call(this,r[o]);for(var n=0,h=i.length;h>n;n++)-1===t.inArray(i[n],r)&&s.call(this,i[n])},s.find_collisions=function(i){for(var e=this,s=this.options.overlapping_region,r=[],o=[],a=this.colliders||this.$colliders,n=a.length,h=e.$element.coords().update(i||!1).get();n--;){var _=e.$colliders?t(a[n]):a[n],d=_.isCoords?_:_.coords(),l=d.get(),c=e.overlaps(h,l);if(c){var p=e.detect_overlapping_region(h,l);if(p===s||"all"===s){var g=e.calculate_overlapped_area_coords(h,l),u=e.calculate_overlapped_area(g),f={area:u,area_coords:g,region:p,coords:l,player_coords:h,el:_};e.options.on_overlap&&e.options.on_overlap.call(this,f),r.push(d),o.push(f)}}}return(e.options.on_overlap_stop||e.options.on_overlap_start)&&this.manage_colliders_start_stop(r,e.options.on_overlap_start,e.options.on_overlap_stop),this.last_colliders_coords=r,o},s.get_closest_colliders=function(t){var i=this.find_collisions(t);return i.sort(function(t,i){return"C"===t.region&&"C"===i.region?t.coords.y1this.player_max_left?o=this.player_max_left:this.player_min_left>o&&(o=this.player_min_left)),{position:{left:o,top:a},pointer:{left:e.left,top:e.top,diff_left:s+(t(window).scrollLeft()-this.win_offset_x),diff_top:r+(t(window).scrollTop()-this.win_offset_y)}}},_.get_drag_data=function(t){var i=this.get_offset(t);return i.$player=this.$player,i.$helper=this.helper?this.$helper:this.$player,i},_.set_limits=function(t){return t||(t=this.$container.width()),this.player_max_left=t-this.player_width+-this.options.offset_left,this.options.container_width=t,this},_.scroll_in=function(i,e){var o,n=r[i],h=50,_=30,d="x"===i,l=d?this.window_width:this.window_height,c=d?t(document).width():t(document).height(),p=d?this.$player.width():this.$player.height(),g=s["scroll"+a(n)](),u=g,f=u+l,w=f-h,m=u+h,y=u+e.pointer[n],v=c-l+p;return y>=w&&(o=g+_,v>o&&(s["scroll"+a(n)](o),this["scroll_offset_"+i]+=_)),m>=y&&(o=g-_,o>0&&(s["scroll"+a(n)](o),this["scroll_offset_"+i]-=_)),this},_.manage_scroll=function(t){this.scroll_in("x",t),this.scroll_in("y",t)},_.calculate_dimensions=function(){this.window_height=s.height(),this.window_width=s.width()},_.drag_handler=function(i){if(i.target.nodeName,!this.disabled&&(1===i.which||o)&&!this.ignore_drag(i)){var e=this,s=!0;return this.$player=t(i.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(i),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.$document.on(this.pointer_events.move,function(t){var i=e.get_mouse_pos(t),r=Math.abs(i.left-e.mouse_init_pos.left),o=Math.abs(i.top-e.mouse_init_pos.top);return r>e.options.distance||o>e.options.distance?s?(s=!1,e.on_dragstart.call(e,t),!1):(e.is_dragging===!0&&e.on_dragmove.call(e,t),!1):!1}),o?void 0:!1}},_.on_dragstart=function(i){if(i.preventDefault(),this.is_dragging)return this;this.drag_start=this.is_dragging=!0;var e=this.$container.offset();return this.baseX=Math.round(e.left),this.baseY=Math.round(e.top),this.initial_container_width=this.options.container_width||this.$container.width(),"clone"===this.options.helper?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.win_offset_y=t(window).scrollTop(),this.win_offset_x=t(window).scrollLeft(),this.scroll_offset_y=0,this.scroll_offset_x=0,this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.player_height=this.$player.height(),this.set_limits(this.options.container_width),this.options.start&&this.options.start.call(this.$player,i,this.get_drag_data(i)),!1},_.on_dragmove=function(t){var i=this.get_drag_data(t);this.options.autoscroll&&this.manage_scroll(i),this.options.move_element&&(this.helper?this.$helper:this.$player).css({position:"absolute",left:i.position.left,top:i.position.top});var e=this.last_position||i.position;return i.prev_position=e,this.options.drag&&this.options.drag.call(this.$player,t,i),this.last_position=i.position,!1},_.on_dragstop=function(t){var i=this.get_drag_data(t);return this.drag_start=!1,this.options.stop&&this.options.stop.call(this.$player,t,i),this.helper&&this.options.remove_helper&&this.$helper.remove(),!1},_.on_select_start=function(t){return this.disabled||this.ignore_drag(t)?void 0:!1},_.enable=function(){this.disabled=!1},_.disable=function(){this.disabled=!0},_.destroy=function(){this.disable(),this.$container.off(this.ns),this.$document.off(this.ns),t(window).off(this.ns),t.removeData(this.$container,"drag")},_.ignore_drag=function(i){return this.options.handle?!t(i.target).is(this.options.handle):t.isFunction(this.options.ignore_dragging)?this.options.ignore_dragging(i):t(i.target).is(this.options.ignore_dragging.join(", "))},t.fn.drag=function(t){return new i(this,t)},i}),function(t,i){"function"==typeof define&&define.amd?define(["jquery","gridster-draggable","gridster-collision"],i):t.Gridster=i(t.$||t.jQuery,t.GridsterDraggable,t.GridsterCollision)}(this,function(t,i){function e(i,e){this.options=t.extend(!0,{},s,e),this.$el=t(i),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs-w"),this.widgets=[],this.$changed=t([]),this.wrapper_width=this.$wrapper.width(),this.min_widget_width=2*this.options.widget_margins[0]+this.options.widget_base_dimensions[0],this.min_widget_height=2*this.options.widget_margins[1]+this.options.widget_base_dimensions[1],this.generated_stylesheets=[],this.$style_tags=t([]),this.options.auto_init&&this.init()}var s={namespace:"",widget_selector:"li",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,max_cols:1/0,min_rows:15,max_size_x:!1,autogrow_cols:!1,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,auto_init:!0,serialize_params:function(t,i){return{col:i.col,row:i.row,size_x:i.size_x,size_y:i.size_y}},collision:{},draggable:{items:".gs-w",distance:4,ignore_dragging:i.defaults.ignore_dragging.slice(0)},resize:{enabled:!1,axes:["both"],handle_append_to:"",handle_class:"gs-resize-handle",max_size:[1/0,1/0],min_size:[1,1]}};e.defaults=s,e.generated_stylesheets=[],e.sort_by_row_asc=function(i){return i=i.sort(function(i,e){return i.row||(i=t(i).coords().grid,e=t(e).coords().grid),i.row>e.row?1:-1})},e.sort_by_row_and_col_asc=function(t){return t=t.sort(function(t,i){return t.row>i.row||t.row===i.row&&t.col>i.col?1:-1})},e.sort_by_col_asc=function(t){return t=t.sort(function(t,i){return t.col>i.col?1:-1})},e.sort_by_row_desc=function(t){return t=t.sort(function(t,i){return t.row+t.size_yn&&this.add_faux_rows(Math.max(e-n,0));var d=o+i-1;d>this.cols&&this.add_faux_cols(d-this.cols);var l={col:_,row:r.row,size_x:i,size_y:e};return this.mutate_widget_in_gridmap(t,r,l),this.set_dom_grid_height(),this.set_dom_grid_width(),s&&s.call(this,l.size_x,l.size_y),t},r.mutate_widget_in_gridmap=function(i,e,s){e.size_x;var r=e.size_y,o=this.get_cells_occupied(e),a=this.get_cells_occupied(s),n=[];t.each(o.cols,function(i,e){-1===t.inArray(e,a.cols)&&n.push(e)});var h=[];t.each(a.cols,function(i,e){-1===t.inArray(e,o.cols)&&h.push(e)});var _=[];t.each(o.rows,function(i,e){-1===t.inArray(e,a.rows)&&_.push(e)});var d=[];if(t.each(a.rows,function(i,e){-1===t.inArray(e,o.rows)&&d.push(e)}),this.remove_from_gridmap(e),h.length){var l=[s.col,s.row,s.size_x,Math.min(r,s.size_y),i];this.empty_cells.apply(this,l)}if(d.length){var c=[s.col,s.row,s.size_x,s.size_y,i];this.empty_cells.apply(this,c)}if(e.col=s.col,e.row=s.row,e.size_x=s.size_x,e.size_y=s.size_y,this.add_to_gridmap(s,i),i.removeClass("player-revert"),i.data("coords").update({width:s.size_x*this.options.widget_base_dimensions[0]+2*(s.size_x-1)*this.options.widget_margins[0],height:s.size_y*this.options.widget_base_dimensions[1]+2*(s.size_y-1)*this.options.widget_margins[1]}),i.attr({"data-col":s.col,"data-row":s.row,"data-sizex":s.size_x,"data-sizey":s.size_y}),n.length){var p=[n[0],s.row,n.length,Math.min(r,s.size_y),i];this.remove_empty_cells.apply(this,p)}if(_.length){var g=[s.col,s.row,s.size_x,s.size_y,i];this.remove_empty_cells.apply(this,g)}return this.move_widget_up(i),this},r.empty_cells=function(i,e,s,r,o){var a=this.widgets_below({col:i,row:e-r,size_x:s,size_y:r});return a.not(o).each(t.proxy(function(i,s){var o=t(s).coords().grid;if(e+r-1>=o.row){var a=e+r-o.row;this.move_widget_down(t(s),a)}},this)),this.set_dom_grid_height(),this},r.remove_empty_cells=function(i,e,s,r,o){var a=this.widgets_below({col:i,row:e,size_x:s,size_y:r});return a.not(o).each(t.proxy(function(i,e){this.move_widget_up(t(e),r)},this)),this.set_dom_grid_height(),this},r.next_position=function(t,i){t||(t=1),i||(i=1);for(var s,r=this.gridmap,o=r.length,a=[],n=1;o>n;n++){s=r[n].length;for(var h=1;s>=h;h++){var _=this.can_move_to({size_x:t,size_y:i},n,h);_&&a.push({col:n,row:h,size_y:i,size_x:t})}}return a.length?e.sort_by_row_and_col_asc(a)[0]:!1},r.remove_widget=function(i,e,s){var r=i instanceof t?i:t(i),o=r.coords().grid;t.isFunction(e)&&(s=e,e=!1),this.cells_occupied_by_placeholder={},this.$widgets=this.$widgets.not(r);var a=this.widgets_below(r);return this.remove_from_gridmap(o),r.fadeOut(t.proxy(function(){r.remove(),e||a.each(t.proxy(function(i,e){this.move_widget_up(t(e),o.size_y)},this)),this.set_dom_grid_height(),s&&s.call(this,i)},this)),this},r.remove_all_widgets=function(i){return this.$widgets.each(t.proxy(function(t,e){this.remove_widget(e,!0,i)},this)),this},r.serialize=function(i){return i||(i=this.$widgets),i.map(t.proxy(function(i,e){var s=t(e);return this.options.serialize_params(s,s.coords().grid)},this)).get()},r.serialize_changed=function(){return this.serialize(this.$changed)},r.dom_to_coords=function(t){return{col:parseInt(t.attr("data-col"),10),row:parseInt(t.attr("data-row"),10),size_x:parseInt(t.attr("data-sizex"),10)||1,size_y:parseInt(t.attr("data-sizey"),10)||1,max_size_x:parseInt(t.attr("data-max-sizex"),10)||!1,max_size_y:parseInt(t.attr("data-max-sizey"),10)||!1,min_size_x:parseInt(t.attr("data-min-sizex"),10)||!1,min_size_y:parseInt(t.attr("data-min-sizey"),10)||!1,el:t}},r.register_widget=function(i){var e=i instanceof jQuery,s=e?this.dom_to_coords(i):i,r=!1;e||(i=s.el);var o=this.can_go_widget_up(s);return o&&(s.row=o,i.attr("data-row",o),this.$el.trigger("gridster:positionchanged",[s]),r=!0),this.options.avoid_overlapped_widgets&&!this.can_move_to({size_x:s.size_x,size_y:s.size_y},s.col,s.row)&&(t.extend(s,this.next_position(s.size_x,s.size_y)),i.attr({"data-col":s.col,"data-row":s.row,"data-sizex":s.size_x,"data-sizey":s.size_y}),r=!0),i.data("coords",i.coords()),i.data("coords").grid=s,this.add_to_gridmap(s,i),this.options.resize.enabled&&this.add_resize_handle(i),r},r.update_widget_position=function(t,i){return this.for_each_cell_occupied(t,function(t,e){return this.gridmap[t]?(this.gridmap[t][e]=i,void 0):this}),this},r.remove_from_gridmap=function(t){return this.update_widget_position(t,!1)},r.add_to_gridmap=function(i,e){if(this.update_widget_position(i,e||i.el),i.el){var s=this.widgets_below(i.el);s.each(t.proxy(function(i,e){this.move_widget_up(t(e))},this))}},r.draggable=function(){var i=this,e=t.extend(!0,{},this.options.draggable,{offset_left:this.options.widget_margins[0],offset_top:this.options.widget_margins[1],container_width:this.cols*this.min_widget_width,limit:!0,start:function(e,s){i.$widgets.filter(".player-revert").removeClass("player-revert"),i.$player=t(this),i.$helper=t(s.$helper),i.helper=!i.$helper.is(i.$player),i.on_start_drag.call(i,e,s),i.$el.trigger("gridster:dragstart")},stop:function(t,e){i.on_stop_drag.call(i,t,e),i.$el.trigger("gridster:dragstop")},drag:throttle(function(t,e){i.on_drag.call(i,t,e),i.$el.trigger("gridster:drag")},60)});return this.drag_api=this.$el.drag(e),this},r.resizable=function(){return this.resize_api=this.$el.drag({items:"."+this.options.resize.handle_class,offset_left:this.options.widget_margins[0],container_width:this.container_width,move_element:!1,resize:!0,limit:this.options.autogrow_cols?!1:!0,start:t.proxy(this.on_start_resize,this),stop:t.proxy(function(i,e){delay(t.proxy(function(){this.on_stop_resize(i,e)},this),120)},this),drag:throttle(t.proxy(this.on_resize,this),60)}),this},r.setup_resize=function(){this.resize_handle_class=this.options.resize.handle_class;var i=this.options.resize.axes,e='';return this.resize_handle_tpl=t.map(i,function(t){return e.replace("{type}",t)}).join(""),t.isArray(this.options.draggable.ignore_dragging)&&this.options.draggable.ignore_dragging.push("."+this.resize_handle_class),this},r.on_start_drag=function(i,e){this.$helper.add(this.$player).add(this.$wrapper).addClass("dragging"),this.highest_col=this.get_highest_occupied_cell().col,this.$player.addClass("player"),this.player_grid_data=this.$player.coords().grid,this.placeholder_grid_data=t.extend({},this.player_grid_data),this.set_dom_grid_height(this.$el.height()+this.player_grid_data.size_y*this.min_widget_height),this.set_dom_grid_width(this.cols);var s=this.player_grid_data.size_x,r=this.cols-this.highest_col;this.options.autogrow_cols&&s>=r&&this.add_faux_cols(Math.min(s-r,1));var o=this.faux_grid,a=this.$player.data("coords").coords;this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.last_cols=[],this.last_rows=[],this.collision_api=this.$helper.collision(o,this.options.collision),this.$preview_holder=t("<"+this.$player.get(0).tagName+" />",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:a.width,height:a.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,i,e)},r.on_drag=function(t,i){if(null===this.$player)return!1;var e={left:i.position.left+this.baseX,top:i.position.top+this.baseY};if(this.options.autogrow_cols){var s=this.placeholder_grid_data.col+this.placeholder_grid_data.size_x-1;s>=this.cols-1&&this.options.max_cols>=this.cols+1&&(this.add_faux_cols(1),this.set_dom_grid_width(this.cols+1),this.drag_api.set_limits(this.container_width)),this.collision_api.set_colliders(this.faux_grid)}this.colliders_data=this.collision_api.get_closest_colliders(e),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:i.position.left,top:i.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,t,i)},r.on_stop_drag=function(t,i){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging"),i.position.left=i.position.left+this.baseX,i.position.top=i.position.top+this.baseY,this.colliders_data=this.collision_api.get_closest_colliders(i.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$player.addClass("player-revert").removeClass("player").attr({"data-col":this.placeholder_grid_data.col,"data-row":this.placeholder_grid_data.row}).css({left:"",top:""}),this.$changed=this.$changed.add(this.$player),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data),this.set_cells_player_occupies(this.placeholder_grid_data.col,this.placeholder_grid_data.row),this.$player.coords().grid.row=this.placeholder_grid_data.row,this.$player.coords().grid.col=this.placeholder_grid_data.col,this.options.draggable.stop&&this.options.draggable.stop.call(this,t,i),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.set_dom_grid_height(),this.set_dom_grid_width(),this.options.autogrow_cols&&this.drag_api.set_limits(this.cols*this.min_widget_width)},r.on_start_resize=function(i,e){this.$resized_widget=e.$player.closest(".gs-w"),this.resize_coords=this.$resized_widget.coords(),this.resize_wgd=this.resize_coords.grid,this.resize_initial_width=this.resize_coords.coords.width,this.resize_initial_height=this.resize_coords.coords.height,this.resize_initial_sizex=this.resize_coords.grid.size_x,this.resize_initial_sizey=this.resize_coords.grid.size_y,this.resize_initial_col=this.resize_coords.grid.col,this.resize_last_sizex=this.resize_initial_sizex,this.resize_last_sizey=this.resize_initial_sizey,this.resize_max_size_x=Math.min(this.resize_wgd.max_size_x||this.options.resize.max_size[0],this.options.max_cols-this.resize_initial_col+1),this.resize_max_size_y=this.resize_wgd.max_size_y||this.options.resize.max_size[1],this.resize_min_size_x=this.resize_wgd.min_size_x||this.options.resize.min_size[0]||1,this.resize_min_size_y=this.resize_wgd.min_size_y||this.options.resize.min_size[1]||1,this.resize_initial_last_col=this.get_highest_occupied_cell().col,this.set_dom_grid_width(this.cols),this.resize_dir={right:e.$player.is("."+this.resize_handle_class+"-x"),bottom:e.$player.is("."+this.resize_handle_class+"-y")},this.$resized_widget.css({"min-width":this.options.widget_base_dimensions[0],"min-height":this.options.widget_base_dimensions[1]});var s=this.$resized_widget.get(0).tagName;this.$resize_preview_holder=t("<"+s+" />",{"class":"preview-holder resize-preview-holder","data-row":this.$resized_widget.attr("data-row"),"data-col":this.$resized_widget.attr("data-col"),css:{width:this.resize_initial_width,height:this.resize_initial_height}}).appendTo(this.$el),this.$resized_widget.addClass("resizing"),this.options.resize.start&&this.options.resize.start.call(this,i,e,this.$resized_widget),this.$el.trigger("gridster:resizestart")},r.on_stop_resize=function(i,e){this.$resized_widget.removeClass("resizing").css({width:"",height:""}),delay(t.proxy(function(){this.$resize_preview_holder.remove().css({"min-width":"","min-height":""}),this.options.resize.stop&&this.options.resize.stop.call(this,i,e,this.$resized_widget),this.$el.trigger("gridster:resizestop")},this),300),this.set_dom_grid_width(),this.options.autogrow_cols&&this.drag_api.set_limits(this.cols*this.min_widget_width)},r.on_resize=function(t,i){var e,s=i.pointer.diff_left,r=i.pointer.diff_top,o=this.options.widget_base_dimensions[0],a=this.options.widget_base_dimensions[1],n=this.options.widget_margins[0],h=this.options.widget_margins[1],_=this.resize_max_size_x,d=this.resize_min_size_x,l=this.resize_max_size_y,c=this.resize_min_size_y,p=this.options.autogrow_cols,g=1/0,u=1/0,f=Math.ceil(s/(o+2*n)-.2),w=Math.ceil(r/(a+2*h)-.2),m=Math.max(1,this.resize_initial_sizex+f),y=Math.max(1,this.resize_initial_sizey+w),v=this.container_width/this.min_widget_width-this.resize_initial_col+1,z=v*this.min_widget_width-2*n;if(m=Math.max(Math.min(m,_),d),m=Math.min(v,m),e=_*o+2*(m-1)*n,g=Math.min(e,z),min_width=d*o+2*(m-1)*n,y=Math.max(Math.min(y,l),c),u=l*a+2*(y-1)*h,min_height=c*a+2*(y-1)*h,this.resize_dir.right?y=this.resize_initial_sizey:this.resize_dir.bottom&&(m=this.resize_initial_sizex),p){var x=this.resize_initial_col+m-1;p&&x>=this.resize_initial_last_col&&(this.set_dom_grid_width(Math.max(x+1,this.cols)),x>this.cols&&this.add_faux_cols(x-this.cols))}var $={};!this.resize_dir.bottom&&($.width=Math.max(Math.min(this.resize_initial_width+s,g),min_width)),!this.resize_dir.right&&($.height=Math.max(Math.min(this.resize_initial_height+r,u),min_height)),this.$resized_widget.css($),(m!==this.resize_last_sizex||y!==this.resize_last_sizey)&&(this.resize_widget(this.$resized_widget,m,y),this.set_dom_grid_width(this.cols),this.$resize_preview_holder.css({width:"",height:""}).attr({"data-row":this.$resized_widget.attr("data-row"),"data-sizex":m,"data-sizey":y})),this.options.resize.resize&&this.options.resize.resize.call(this,t,i,this.$resized_widget),this.$el.trigger("gridster:resize"),this.resize_last_sizex=m,this.resize_last_sizey=y},r.on_overlapped_column_change=function(i,e){if(!this.colliders_data.length)return this;var s,r=this.get_targeted_columns(this.colliders_data[0].el.data.col),o=this.last_cols.length,a=r.length;for(s=0;a>s;s++)-1===t.inArray(r[s],this.last_cols)&&(i||t.noop).call(this,r[s]);for(s=0;o>s;s++)-1===t.inArray(this.last_cols[s],r)&&(e||t.noop).call(this,this.last_cols[s]);return this.last_cols=r,this},r.on_overlapped_row_change=function(i,e){if(!this.colliders_data.length)return this;var s,r=this.get_targeted_rows(this.colliders_data[0].el.data.row),o=this.last_rows.length,a=r.length;for(s=0;a>s;s++)-1===t.inArray(r[s],this.last_rows)&&(i||t.noop).call(this,r[s]);for(s=0;o>s;s++)-1===t.inArray(this.last_rows[s],r)&&(e||t.noop).call(this,this.last_rows[s]);this.last_rows=r},r.set_player=function(t,i,e){var s=this;e||this.empty_cells_player_occupies();var r=e?{col:t}:s.colliders_data[0].el.data,o=r.col,a=i||r.row;this.player_grid_data={col:o,row:a,size_y:this.player_grid_data.size_y,size_x:this.player_grid_data.size_x},this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data);var n=this.get_widgets_overlapped(this.player_grid_data),h=this.widgets_constraints(n);if(this.manage_movements(h.can_go_up,o,a),this.manage_movements(h.can_not_go_up,o,a),!n.length){var _=this.can_go_player_up(this.player_grid_data);_!==!1&&(a=_),this.set_placeholder(o,a)}return{col:o,row:a}},r.widgets_constraints=function(i){var s,r=t([]),o=[],a=[];return i.each(t.proxy(function(i,e){var s=t(e),n=s.coords().grid;this.can_go_widget_up(n)?(r=r.add(s),o.push(n)):a.push(n)},this)),s=i.not(r),{can_go_up:e.sort_by_row_asc(o),can_not_go_up:e.sort_by_row_desc(a)}},r.manage_movements=function(i,e,s){return t.each(i,t.proxy(function(t,i){var r=i,o=r.el,a=this.can_go_widget_up(r);if(a)this.move_widget_to(o,a),this.set_placeholder(e,a+r.size_y);else{var n=this.can_go_player_up(this.player_grid_data);if(!n){var h=s+this.player_grid_data.size_y-r.row;this.move_widget_down(o,h),this.set_placeholder(e,s)}}},this)),this},r.is_player=function(t,i){if(i&&!this.gridmap[t])return!1;var e=i?this.gridmap[t][i]:t;return e&&(e.is(this.$player)||e.is(this.$helper))},r.is_player_in=function(i,e){var s=this.cells_occupied_by_player||{};return t.inArray(i,s.cols)>=0&&t.inArray(e,s.rows)>=0},r.is_placeholder_in=function(i,e){var s=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(i)&&t.inArray(e,s.rows)>=0},r.is_placeholder_in_col=function(i){var e=this.cells_occupied_by_placeholder||[];return t.inArray(i,e.cols)>=0},r.is_empty=function(t,i){return this.gridmap[t]!==void 0?this.gridmap[t][i]!==void 0&&this.gridmap[t][i]===!1?!0:!1:!0},r.is_occupied=function(t,i){return this.gridmap[t]?this.gridmap[t][i]?!0:!1:!1},r.is_widget=function(t,i){var e=this.gridmap[t];return e?(e=e[i],e?e:!1):!1},r.is_widget_under_player=function(t,i){return this.is_widget(t,i)?this.is_player_in(t,i):!1},r.get_widgets_under_player=function(i){i||(i=this.cells_occupied_by_player||{cols:[],rows:[]});var e=t([]);return t.each(i.cols,t.proxy(function(s,r){t.each(i.rows,t.proxy(function(t,i){this.is_widget(r,i)&&(e=e.add(this.gridmap[r][i]))},this))},this)),e},r.set_placeholder=function(i,e){var s=t.extend({},this.placeholder_grid_data),r=this.widgets_below({col:s.col,row:s.row,size_y:s.size_y,size_x:s.size_x}),o=i+s.size_x-1;o>this.cols&&(i-=o-i);var a=e>this.placeholder_grid_data.row,n=this.placeholder_grid_data.col!==i;this.placeholder_grid_data.col=i,this.placeholder_grid_data.row=e,this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.$preview_holder.attr({"data-row":e,"data-col":i}),(a||n)&&r.each(t.proxy(function(e,r){this.move_widget_up(t(r),this.placeholder_grid_data.col-i+s.size_y)},this));var h=this.get_widgets_under_player(this.cells_occupied_by_placeholder);h.length&&h.each(t.proxy(function(i,r){var o=t(r);this.move_widget_down(o,e+s.size_y-o.data("coords").grid.row)},this))},r.can_go_player_up=function(t){var i=t.row+t.size_y-1,e=!0,s=[],r=1e4,o=this.get_widgets_under_player();return this.for_each_column_occupied(t,function(t){var a=this.gridmap[t],n=i+1;for(s[t]=[];--n>0&&(this.is_empty(t,n)||this.is_player(t,n)||this.is_widget(t,n)&&a[n].is(o));)s[t].push(n),r=r>n?n:r;return 0===s[t].length?(e=!1,!0):(s[t].sort(function(t,i){return t-i -}),void 0)}),e?this.get_valid_rows(t,s,r):!1},r.can_go_widget_up=function(t){var i=t.row+t.size_y-1,e=!0,s=[],r=1e4;return this.for_each_column_occupied(t,function(o){var a=this.gridmap[o];s[o]=[];for(var n=i+1;--n>0&&(!this.is_widget(o,n)||this.is_player_in(o,n)||a[n].is(t.el));)this.is_player(o,n)||this.is_placeholder_in(o,n)||this.is_player_in(o,n)||s[o].push(n),r>n&&(r=n);return 0===s[o].length?(e=!1,!0):(s[o].sort(function(t,i){return t-i}),void 0)}),e?this.get_valid_rows(t,s,r):!1},r.get_valid_rows=function(i,e,s){for(var r=i.row,o=i.row+i.size_y-1,a=i.size_y,n=s-1,h=[];o>=++n;){var _=!0;if(t.each(e,function(i,e){t.isArray(e)&&-1===t.inArray(n,e)&&(_=!1)}),_===!0&&(h.push(n),h.length===a))break}var d=!1;return 1===a?h[0]!==r&&(d=h[0]||!1):h[0]!==r&&(d=this.get_consecutive_numbers_index(h,a)),d},r.get_consecutive_numbers_index=function(t,i){for(var e=t.length,s=[],r=!0,o=-1,a=0;e>a;a++){if(r||t[a]===o+1){if(s.push(a),s.length===i)break;r=!1}else s=[],r=!0;o=t[a]}return s.length>=i?t[s[0]]:!1},r.get_widgets_overlapped=function(){var i=t([]),e=[],s=this.cells_occupied_by_player.rows.slice(0);return s.reverse(),t.each(this.cells_occupied_by_player.cols,t.proxy(function(r,o){t.each(s,t.proxy(function(s,r){if(!this.gridmap[o])return!0;var a=this.gridmap[o][r];this.is_occupied(o,r)&&!this.is_player(a)&&-1===t.inArray(a,e)&&(i=i.add(a),e.push(a))},this))},this)),i},r.on_start_overlapping_column=function(t){this.set_player(t,!1)},r.on_start_overlapping_row=function(t){this.set_player(!1,t)},r.on_stop_overlapping_column=function(t){this.set_player(t,!1);var i=this;this.for_each_widget_below(t,this.cells_occupied_by_player.rows[0],function(){i.move_widget_up(this,i.player_grid_data.size_y)})},r.on_stop_overlapping_row=function(t){this.set_player(!1,t);for(var i=this,e=this.cells_occupied_by_player.cols,s=0,r=e.length;r>s;s++)this.for_each_widget_below(e[s],t,function(){i.move_widget_up(this,i.player_grid_data.size_y)})},r.move_widget_to=function(i,e){var s=this,r=i.coords().grid;e-r.row;var o=this.widgets_below(i),a=this.can_move_to(r,r.col,e,i);return a===!1?!1:(this.remove_from_gridmap(r),r.row=e,this.add_to_gridmap(r),i.attr("data-row",e),this.$changed=this.$changed.add(i),o.each(function(i,e){var r=t(e),o=r.coords().grid,a=s.can_go_widget_up(o);a&&a!==o.row&&s.move_widget_to(r,a)}),this)},r.move_widget_up=function(i,e){var s=i.coords().grid,r=s.row,o=[];return e||(e=1),this.can_go_up(i)?(this.for_each_column_occupied(s,function(s){if(-1===t.inArray(i,o)){var a=i.coords().grid,n=r-e;if(n=this.can_go_up_to_row(a,s,n),!n)return!0;var h=this.widgets_below(i);this.remove_from_gridmap(a),a.row=n,this.add_to_gridmap(a),i.attr("data-row",a.row),this.$changed=this.$changed.add(i),o.push(i),h.each(t.proxy(function(i,s){this.move_widget_up(t(s),e)},this))}}),void 0):!1},r.move_widget_down=function(i,e){var s,r,o,a;if(0>=e)return!1;if(s=i.coords().grid,r=s.row,o=[],a=e,!i)return!1;if(-1===t.inArray(i,o)){var n=i.coords().grid,h=r+e,_=this.widgets_below(i);this.remove_from_gridmap(n),_.each(t.proxy(function(i,e){var s=t(e),r=s.coords().grid,o=this.displacement_diff(r,n,a);o>0&&this.move_widget_down(s,o)},this)),n.row=h,this.update_widget_position(n,i),i.attr("data-row",n.row),this.$changed=this.$changed.add(i),o.push(i)}},r.can_go_up_to_row=function(i,e,s){var r,o=this.gridmap,a=!0,n=[],h=i.row;if(this.for_each_column_occupied(i,function(t){for(o[t],n[t]=[],r=h;r--&&this.is_empty(t,r)&&!this.is_placeholder_in(t,r);)n[t].push(r);return n[t].length?void 0:(a=!1,!0)}),!a)return!1;for(r=s,r=1;h>r;r++){for(var _=!0,d=0,l=n.length;l>d;d++)n[d]&&-1===t.inArray(r,n[d])&&(_=!1);if(_===!0){a=r;break}}return a},r.displacement_diff=function(t,i,e){var s=t.row,r=[],o=i.row+i.size_y;this.for_each_column_occupied(t,function(t){for(var i=0,e=o;s>e;e++)this.is_empty(t,e)&&(i+=1);r.push(i)});var a=Math.max.apply(Math,r);return e-=a,e>0?e:0},r.widgets_below=function(i){var s=t.isPlainObject(i)?i:i.coords().grid,r=this;this.gridmap;var o=s.row+s.size_y-1,a=t([]);return this.for_each_column_occupied(s,function(i){r.for_each_widget_below(i,o,function(){return r.is_player(this)||-1!==t.inArray(this,a)?void 0:(a=a.add(this),!0)})}),e.sort_by_row_asc(a)},r.set_cells_player_occupies=function(t,i){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=t,this.placeholder_grid_data.row=i,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},r.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},r.can_go_up=function(t){var i=t.coords().grid,e=i.row,s=e-1;this.gridmap;var r=!0;return 1===e?!1:(this.for_each_column_occupied(i,function(t){return this.is_widget(t,s),this.is_occupied(t,s)||this.is_player(t,s)||this.is_placeholder_in(t,s)||this.is_player_in(t,s)?(r=!1,!0):void 0}),r)},r.can_move_to=function(t,i,e,s){this.gridmap;var r=t.el,o={size_y:t.size_y,size_x:t.size_x,col:i,row:e},a=!0,n=i+t.size_x-1;return n>this.cols?!1:s&&e+t.size_y-1>s?!1:(this.for_each_cell_occupied(o,function(i,e){var s=this.is_widget(i,e);!s||t.el&&!s.is(r)||(a=!1)}),a)},r.get_targeted_columns=function(t){for(var i=(t||this.player_grid_data.col)+(this.player_grid_data.size_x-1),e=[],s=t;i>=s;s++)e.push(s);return e},r.get_targeted_rows=function(t){for(var i=(t||this.player_grid_data.row)+(this.player_grid_data.size_y-1),e=[],s=t;i>=s;s++)e.push(s);return e},r.get_cells_occupied=function(i){var e,s={cols:[],rows:[]};for(arguments[1]instanceof t&&(i=arguments[1].coords().grid),e=0;i.size_x>e;e++){var r=i.col+e;s.cols.push(r)}for(e=0;i.size_y>e;e++){var o=i.row+e;s.rows.push(o)}return s},r.for_each_cell_occupied=function(t,i){return this.for_each_column_occupied(t,function(e){this.for_each_row_occupied(t,function(t){i.call(this,e,t)})}),this},r.for_each_column_occupied=function(t,i){for(var e=0;t.size_x>e;e++){var s=t.col+e;i.call(this,s,t)}},r.for_each_row_occupied=function(t,i){for(var e=0;t.size_y>e;e++){var s=t.row+e;i.call(this,s,t)}},r._traversing_widgets=function(i,e,s,r,o){var a=this.gridmap;if(a[s]){var n,h,_=i+"/"+e;if(arguments[2]instanceof t){var d=arguments[2].coords().grid;s=d.col,r=d.row,o=arguments[3]}var l=[],c=r,p={"for_each/above":function(){for(;c--&&!(c>0&&this.is_widget(s,c)&&-1===t.inArray(a[s][c],l)&&(n=o.call(a[s][c],s,c),l.push(a[s][c]),n)););},"for_each/below":function(){for(c=r+1,h=a[s].length;h>c&&(!this.is_widget(s,c)||-1!==t.inArray(a[s][c],l)||(n=o.call(a[s][c],s,c),l.push(a[s][c]),!n));c++);}};p[_]&&p[_].call(this)}},r.for_each_widget_above=function(t,i,e){return this._traversing_widgets("for_each","above",t,i,e),this},r.for_each_widget_below=function(t,i,e){return this._traversing_widgets("for_each","below",t,i,e),this},r.get_highest_occupied_cell=function(){for(var t,i=this.gridmap,e=i[1].length,s=[],r=[],o=i.length-1;o>=1;o--)for(t=e-1;t>=1;t--)if(this.is_widget(o,t)){s.push(t),r.push(o);break}return{col:Math.max.apply(Math,r),row:Math.max.apply(Math,s)}},r.get_widgets_from=function(i,e){this.gridmap;var s=t();return i&&(s=s.add(this.$widgets.filter(function(){var e=t(this).attr("data-col");return e===i||e>i}))),e&&(s=s.add(this.$widgets.filter(function(){var i=t(this).attr("data-row");return i===e||i>e}))),s},r.set_dom_grid_height=function(t){if(t===void 0){var i=this.get_highest_occupied_cell().row;t=i*this.min_widget_height}return this.container_height=t,this.$el.css("height",this.container_height),this},r.set_dom_grid_width=function(t){t===void 0&&(t=this.get_highest_occupied_cell().col);var i=this.options.autogrow_cols?this.options.max_cols:this.cols;return t=Math.min(i,Math.max(t,this.options.min_cols)),this.container_width=t*this.min_widget_width,this.$el.css("width",this.container_width),this},r.generate_stylesheet=function(i){var s,r="",o=this.options.max_size_x||this.cols;i||(i={}),i.cols||(i.cols=this.cols),i.rows||(i.rows=this.rows),i.namespace||(i.namespace=this.options.namespace),i.widget_base_dimensions||(i.widget_base_dimensions=this.options.widget_base_dimensions),i.widget_margins||(i.widget_margins=this.options.widget_margins),i.min_widget_width=2*i.widget_margins[0]+i.widget_base_dimensions[0],i.min_widget_height=2*i.widget_margins[1]+i.widget_base_dimensions[1];var a=t.param(i);if(t.inArray(a,e.generated_stylesheets)>=0)return!1;for(this.generated_stylesheets.push(a),e.generated_stylesheets.push(a),s=i.cols;s>=0;s--)r+=i.namespace+' [data-col="'+(s+1)+'"] { left:'+(s*i.widget_base_dimensions[0]+s*i.widget_margins[0]+(s+1)*i.widget_margins[0])+"px; }\n";for(s=i.rows;s>=0;s--)r+=i.namespace+' [data-row="'+(s+1)+'"] { top:'+(s*i.widget_base_dimensions[1]+s*i.widget_margins[1]+(s+1)*i.widget_margins[1])+"px; }\n";for(var n=1;i.rows>=n;n++)r+=i.namespace+' [data-sizey="'+n+'"] { height:'+(n*i.widget_base_dimensions[1]+(n-1)*2*i.widget_margins[1])+"px; }\n";for(var h=1;o>=h;h++)r+=i.namespace+' [data-sizex="'+h+'"] { width:'+(h*i.widget_base_dimensions[0]+(h-1)*2*i.widget_margins[0])+"px; }\n";return this.remove_style_tags(),this.add_style_tag(r)},r.add_style_tag=function(t){var i=document,e=i.createElement("style");return i.getElementsByTagName("head")[0].appendChild(e),e.setAttribute("type","text/css"),e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t)),this.$style_tags=this.$style_tags.add(e),this},r.remove_style_tags=function(){var i=e.generated_stylesheets,s=this.generated_stylesheets;this.$style_tags.remove(),e.generated_stylesheets=t.map(i,function(i){return-1===t.inArray(i,s)?i:void 0})},r.generate_faux_grid=function(t,i){this.faux_grid=[],this.gridmap=[];var e,s;for(e=i;e>0;e--)for(this.gridmap[e]=[],s=t;s>0;s--)this.add_faux_cell(s,e);return this},r.add_faux_cell=function(i,e){var s=t({left:this.baseX+(e-1)*this.min_widget_width,top:this.baseY+(i-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:e,row:i,original_col:e,original_row:i}).coords();return t.isArray(this.gridmap[e])||(this.gridmap[e]=[]),this.gridmap[e][i]=!1,this.faux_grid.push(s),this},r.add_faux_rows=function(t){for(var i=this.rows,e=i+(t||1),s=e;s>i;s--)for(var r=this.cols;r>=1;r--)this.add_faux_cell(s,r);return this.rows=e,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},r.add_faux_cols=function(t){var i=this.cols,e=i+(t||1);e=Math.min(e,this.options.max_cols);for(var s=i+1;e>=s;s++)for(var r=this.rows;r>=1;r--)this.add_faux_cell(r,s);return this.cols=e,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},r.recalculate_faux_grid=function(){var i=this.$wrapper.width();return this.baseX=(t(window).width()-i)/2,this.baseY=this.$wrapper.offset().top,t.each(this.faux_grid,t.proxy(function(t,i){this.faux_grid[t]=i.update({left:this.baseX+(i.data.col-1)*this.min_widget_width,top:this.baseY+(i.data.row-1)*this.min_widget_height})},this)),this},r.get_widgets_from_DOM=function(){var i=this.$widgets.map(t.proxy(function(i,e){var s=t(e);return this.dom_to_coords(s)},this));i=e.sort_by_row_and_col_asc(i);var s=t(i).map(t.proxy(function(t,i){return this.register_widget(i)||null},this));return s.length&&this.$el.trigger("gridster:positionschanged"),this},r.generate_grid_and_stylesheet=function(){var i=this.$wrapper.width(),e=this.options.max_cols,s=Math.floor(i/this.min_widget_width)+this.options.extra_cols,r=this.$widgets.map(function(){return t(this).attr("data-col")}).get();r.length||(r=[0]);var o=Math.max.apply(Math,r);this.cols=Math.max(o,s,this.options.min_cols),1/0!==e&&e>=o&&this.cols>e&&(this.cols=e);var a=this.options.extra_rows;return this.$widgets.each(function(i,e){a+=+t(e).attr("data-sizey")}),this.rows=Math.max(a,this.options.min_rows),this.baseX=(t(window).width()-i)/2,this.baseY=this.$wrapper.offset().top,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this.generate_faux_grid(this.rows,this.cols)},r.destroy=function(i){return this.$el.removeData("gridster"),t(window).unbind(".gridster"),this.drag_api&&this.drag_api.destroy(),this.remove_style_tags(),i&&this.$el.remove(),this},t.fn.gridster=function(i){return this.each(function(){t(this).data("gridster")||t(this).data("gridster",new e(this,i))})},e}),function(t,i){"function"==typeof define&&define.amd?define(["jquery","gridster"],i):t.Gridster=i(t.$||t.jQuery,t.Gridster)}(this,function(t,i){var e=i.prototype;return e.widgets_in_col=function(t){if(!this.gridmap[t])return!1;for(var i=this.gridmap[t].length-1;i>=0;i--)if(this.is_widget(t,i)!==!1)return!0;return!1},e.widgets_in_row=function(t){for(var i=this.gridmap.length;i>=1;i--)if(this.is_widget(i,t)!==!1)return!0;return!1},e.widgets_in_range=function(i,e,s,r){var o,a,n,h,_=t([]);for(o=s;o>=i;o--)for(a=r;a>=e;a--)n=this.is_widget(o,a),n!==!1&&(h=n.data("coords").grid,h.col>=i&&s>=h.col&&h.row>=e&&r>=h.row&&(_=_.add(n)));return _},e.get_bottom_most_occupied_cell=function(){var t=0,i=0;return this.for_each_cell(function(e,s,r){e&&r>t&&(t=r,i=s)}),{col:i,row:t}},e.get_right_most_occupied_cell=function(){var t=0,i=0;return this.for_each_cell(function(e,s,r){return e?(t=r,i=s,!1):void 0}),{col:i,row:t}},e.for_each_cell=function(t,i){i||(i=this.gridmap);var e=i.length,s=i[1].length;t:for(var r=e-1;r>=1;r--)for(var o=s-1;o>=1;o--){var a=i[r]&&i[r][o];if(t){if(t.call(this,a,r,o)===!1)break t}else;}},e.next_position_in_range=function(t,e,s){t||(t=1),e||(e=1);for(var r,o=this.gridmap,a=o.length,n=[],h=1;a>h;h++){r=s||o[h].length;for(var _=1;r>=_;_++){var d=this.can_move_to({size_x:t,size_y:e},h,_,s);d&&n.push({col:h,row:_,size_y:e,size_x:t})}}return n.length>=1?i.sort_by_col_asc(n)[0]:!1},e.closest_to_right=function(t,i){if(!this.gridmap[t])return!1;for(var e=this.gridmap.length-1,s=t;e>=s;s++)if(this.gridmap[s][i])return{col:s,row:i};return!1},e.closest_to_left=function(t,i){if(this.gridmap.length-1,!this.gridmap[t])return!1;for(var e=t;e>=1;e--)if(this.gridmap[e][i])return{col:e,row:i};return!1},i}); \ No newline at end of file +/*! gridster.js - v0.6.10 - 2015-08-05 - * https://dsmorse.github.io/gridster.js/ - Copyright (c) 2015 ducksboard; Licensed MIT */ !function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define("gridster-coords",["jquery"],b):a.GridsterCoords=b(a.$||a.jQuery)}(this,function(a){"use strict";function b(b){return b[0]&&a.isPlainObject(b[0])?this.data=b[0]:this.el=b,this.isCoords=!0,this.coords={},this.init(),this}var c=b.prototype;return c.init=function(){this.set(),this.original_coords=this.get()},c.set=function(a,b){var c=this.el;if(c&&!a&&(this.data=c.offset(),this.data.width=c[0].scrollWidth,this.data.height=c[0].scrollHeight),c&&a&&!b){var d=c.offset();this.data.top=d.top,this.data.left=d.left}var e=this.data;return void 0===e.left&&(e.left=e.x1),void 0===e.top&&(e.top=e.y1),this.coords.x1=e.left,this.coords.y1=e.top,this.coords.x2=e.left+e.width,this.coords.y2=e.top+e.height,this.coords.cx=e.left+e.width/2,this.coords.cy=e.top+e.height/2,this.coords.width=e.width,this.coords.height=e.height,this.coords.el=c||!1,this},c.update=function(b){if(!b&&!this.el)return this;if(b){var c=a.extend({},this.data,b);return this.data=c,this.set(!0,!0)}return this.set(!0),this},c.get=function(){return this.coords},c.destroy=function(){this.el.removeData("coords"),delete this.el},a.fn.coords=function(){if(this.data("coords"))return this.data("coords");var a=new b(this);return this.data("coords",a),a},b}),function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery")):"function"==typeof define&&define.amd?define("gridster-collision",["jquery","gridster-coords"],b):a.GridsterCollision=b(a.$||a.jQuery,a.GridsterCoords)}(this,function(a,b){"use strict";function c(b,c,e){this.options=a.extend(d,e),this.$element=b,this.last_colliders=[],this.last_colliders_coords=[],this.set_colliders(c),this.init()}var d={colliders_context:document.body,overlapping_region:"C"};c.defaults=d;var e=c.prototype;return e.init=function(){this.find_collisions()},e.overlaps=function(a,b){var c=!1,d=!1;return(b.x1>=a.x1&&b.x1<=a.x2||b.x2>=a.x1&&b.x2<=a.x2||a.x1>=b.x1&&a.x2<=b.x2)&&(c=!0),(b.y1>=a.y1&&b.y1<=a.y2||b.y2>=a.y1&&b.y2<=a.y2||a.y1>=b.y1&&a.y2<=b.y2)&&(d=!0),c&&d},e.detect_overlapping_region=function(a,b){var c="",d="";return a.y1>b.cy&&a.y1b.y1&&a.y2b.cx&&a.x1b.x1&&a.x2f;f++)-1===a.inArray(e[f],b)&&c.call(this,e[f]);for(var h=0,i=b.length;i>h;h++)-1===a.inArray(b[h],e)&&d.call(this,b[h])},e.find_collisions=function(b){for(var c=this,d=this.options.overlapping_region,e=[],f=[],g=this.colliders||this.$colliders,h=g.length,i=c.$element.coords().update(b||!1).get();h--;){var j=c.$colliders?a(g[h]):g[h],k=j.isCoords?j:j.coords(),l=k.get(),m=c.overlaps(i,l);if(m){var n=c.detect_overlapping_region(i,l);if(n===d||"all"===d){var o=c.calculate_overlapped_area_coords(i,l),p=c.calculate_overlapped_area(o);if(0!==p){var q={area:p,area_coords:o,region:n,coords:l,player_coords:i,el:j};c.options.on_overlap&&c.options.on_overlap.call(this,q),e.push(k),f.push(q)}}}}return(c.options.on_overlap_stop||c.options.on_overlap_start)&&this.manage_colliders_start_stop(e,c.options.on_overlap_start,c.options.on_overlap_stop),this.last_colliders_coords=e,f},e.get_closest_colliders=function(a){var b=this.find_collisions(a);return b.sort(function(a,b){return"C"===a.region&&"C"===b.region?a.coords.y1this.player_max_left?e=this.player_max_left:e=q&&(l=n+h,t>l&&(this.$scroll_container[i](l),this["scroll_offset_"+a]+=h)),r>=s&&(l=n-h,l>0&&(this.$scroll_container[i](l),this["scroll_offset_"+a]-=h)),this},j.manage_scroll=function(a){this.scroll_in("x",a),this.scroll_in("y",a)},j.calculate_dimensions=function(){this.scroller_height=this.$scroll_container.height(),this.scroller_width=this.$scroll_container.width()},j.drag_handler=function(b){if(!this.disabled&&(1===b.which||f)&&!this.ignore_drag(b)){var c=this,d=!0;return this.$player=a(b.currentTarget),this.el_init_pos=this.get_actual_pos(this.$player),this.mouse_init_pos=this.get_mouse_pos(b),this.offsetY=this.mouse_init_pos.top-this.el_init_pos.top,this.$document.on(this.pointer_events.move,function(a){var b=c.get_mouse_pos(a),e=Math.abs(b.left-c.mouse_init_pos.left),f=Math.abs(b.top-c.mouse_init_pos.top);return e>c.options.distance||f>c.options.distance?d?(d=!1,c.on_dragstart.call(c,a),!1):(c.is_dragging===!0&&c.on_dragmove.call(c,a),!1):!1}),f?void 0:!1}},j.on_dragstart=function(a){if(a.preventDefault(),this.is_dragging)return this;this.drag_start=this.is_dragging=!0;var b=this.$container.offset();return this.baseX=Math.round(b.left),this.baseY=Math.round(b.top),"clone"===this.options.helper?(this.$helper=this.$player.clone().appendTo(this.$container).addClass("helper"),this.helper=!0):this.helper=!1,this.scroll_container_offset_y=this.$scroll_container.scrollTop(),this.scroll_container_offset_x=this.$scroll_container.scrollLeft(),this.el_init_offset=this.$player.offset(),this.player_width=this.$player.width(),this.set_limits(this.options.container_width),this.options.start&&this.options.start.call(this.$player,a,this.get_drag_data(a)),!1},j.on_dragmove=function(a){var b=this.get_drag_data(a);this.options.autoscroll&&this.manage_scroll(b),this.options.move_element&&(this.helper?this.$helper:this.$player).css({position:"absolute",left:b.position.left,top:b.position.top});var c=this.last_position||b.position;return b.prev_position=c,this.options.drag&&this.options.drag.call(this.$player,a,b),this.last_position=b.position,!1},j.on_dragstop=function(a){var b=this.get_drag_data(a);return this.drag_start=!1,this.options.stop&&this.options.stop.call(this.$player,a,b),this.helper&&this.options.remove_helper&&this.$helper.remove(),!1},j.on_select_start=function(a){return this.disabled||this.ignore_drag(a)?void 0:!1},j.enable=function(){this.disabled=!1},j.disable=function(){this.disabled=!0},j.destroy=function(){this.disable(),this.$container.off(this.ns),this.$document.off(this.ns),d.off(this.ns),a.removeData(this.$container,"drag")},j.ignore_drag=function(b){return this.options.handle?!a(b.target).is(this.options.handle):a.isFunction(this.options.ignore_dragging)?this.options.ignore_dragging(b):this.options.resize?!a(b.target).is(this.options.items):a(b.target).is(this.options.ignore_dragging.join(", "))},a.fn.gridDraggable=function(a){return new b(this,a)},a.fn.dragg=function(c){return this.each(function(){a.data(this,"drag")||a.data(this,"drag",new b(this,c))})},b}),function(a,b){"use strict";"object"==typeof exports?module.exports=b(require("jquery"),require("./jquery.draggable.js"),require("./jquery.collision.js"),require("./jquery.coords.js"),require("./utils.js")):"function"==typeof define&&define.amd?define(["jquery","gridster-draggable","gridster-collision"],b):a.Gridster=b(a.$||a.jQuery,a.GridsterDraggable,a.GridsterCollision)}(this,function(a,b,c){"use strict";function d(b,c){this.options=a.extend(!0,{},g,c),this.options.draggable=this.options.draggable||{},this.options.draggable=a.extend(!0,{},this.options.draggable,{scroll_container:this.options.scroll_container}),this.$el=a(b),this.$scroll_container=this.options.scroll_container===window?a(window):this.$el.closest(this.options.scroll_container),this.$wrapper=this.$el.parent(),this.$widgets=this.$el.children(this.options.widget_selector).addClass("gs-w"),this.$changed=a([]),this.w_queue={},this.is_responsive()?this.min_widget_width=this.get_responsive_col_width():this.min_widget_width=this.options.widget_base_dimensions[0],this.min_widget_height=this.options.widget_base_dimensions[1],this.min_col_count=this.options.min_cols,this.prev_col_count=this.min_col_count,this.generated_stylesheets=[],this.$style_tags=a([]),this.options.auto_init&&this.init()}function e(a){for(var b=["col","row","size_x","size_y"],c={},d=0,e=b.length;e>d;d++){var f=b[d];if(!(f in a))throw new Error("Not exists property `"+f+"`");var g=a[f];if(!g||isNaN(g))throw new Error("Invalid value of `"+f+"` property");c[f]=+g}return c}var f=a(window),g={namespace:"",widget_selector:"li",static_class:"static",widget_margins:[10,10],widget_base_dimensions:[400,225],extra_rows:0,extra_cols:0,min_cols:1,max_cols:1/0,min_rows:1,max_rows:15,autogenerate_stylesheet:!0,avoid_overlapped_widgets:!0,auto_init:!0,center_widgets:!1,responsive_breakpoint:!1,scroll_container:window,shift_larger_widgets_down:!0,shift_widgets_up:!0,show_element:function(a,b){b?a.fadeIn(b):a.fadeIn()},hide_element:function(a,b){b?a.fadeOut(b):a.fadeOut()},serialize_params:function(a,b){return{col:b.col,row:b.row,size_x:b.size_x,size_y:b.size_y}},collision:{wait_for_mouseup:!1},draggable:{items:".gs-w:not(.static)",distance:4,ignore_dragging:b.defaults.ignore_dragging.slice(0)},resize:{enabled:!1,axes:["both"],handle_append_to:"",handle_class:"gs-resize-handle",max_size:[1/0,1/0],min_size:[1,1]}};d.defaults=g,d.generated_stylesheets=[],d.sort_by_row_asc=function(b){return b=b.sort(function(b,c){return b.row||(b=a(b).coords().grid,c=a(c).coords().grid),b=e(b),c=e(c),b.row>c.row?1:-1})},d.sort_by_row_and_col_asc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.row>b.row||a.row===b.row&&a.col>b.col?1:-1})},d.sort_by_col_asc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.col>b.col?1:-1})},d.sort_by_row_desc=function(a){return a=a.sort(function(a,b){return a=e(a),b=e(b),a.row+a.size_yi&&this.add_faux_rows(Math.max(d-i,0));var k={col:j,row:g.row,size_x:c,size_y:d};return this.mutate_widget_in_gridmap(b,g,k),this.set_dom_grid_height(),this.set_dom_grid_width(),f&&f.call(this,k.size_x,k.size_y),b},h.collapse_widget=function(a,b){var c=a.coords().grid,d=parseInt(a.attr("pre_expand_sizex")),e=parseInt(a.attr("pre_expand_sizey")),f=parseInt(a.attr("pre_expand_col")),g={col:f,row:c.row,size_x:d,size_y:e};return this.mutate_widget_in_gridmap(a,c,g),this.set_dom_grid_height(),this.set_dom_grid_width(),b&&b.call(this,g.size_x,g.size_y),a},h.fit_to_content=function(a,b,c,d){var e=a.coords().grid,f=this.$wrapper.width(),g=this.$wrapper.height(),h=this.options.widget_base_dimensions[0]+2*this.options.widget_margins[0],i=this.options.widget_base_dimensions[1]+2*this.options.widget_margins[1],j=Math.ceil((f+2*this.options.widget_margins[0])/h),k=Math.ceil((g+2*this.options.widget_margins[1])/i),l={col:e.col,row:e.row,size_x:Math.min(b,j),size_y:Math.min(c,k)};return this.mutate_widget_in_gridmap(a,e,l),this.set_dom_grid_height(),this.set_dom_grid_width(),d&&d.call(this,l.size_x,l.size_y),a},h.center_widgets=debounce(function(){var b,c=this.$wrapper.width();b=this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0]+2*this.options.widget_margins[0];var d=2*Math.floor(Math.max(Math.floor(c/b),this.min_col_count)/2);this.options.min_cols=d,this.options.max_cols=d,this.options.extra_cols=0,this.set_dom_grid_width(d),this.cols=d;var e=(d-this.prev_col_count)/2;return 0>e?(this.get_min_col()>-1*e?this.shift_cols(e):this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.resize_widget_dimensions(this.options)},this),0)):e>0?(this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.shift_cols(e)},this),0)):(this.resize_widget_dimensions(this.options),setTimeout(a.proxy(function(){this.resize_widget_dimensions(this.options)},this),0)),this.prev_col_count=d,this},200),h.get_min_col=function(){return Math.min.apply(Math,this.$widgets.map(a.proxy(function(b,c){return this.get_cells_occupied(a(c).coords().grid).cols},this)).get())},h.shift_cols=function(b){var c=this.$widgets.map(a.proxy(function(b,c){var d=a(c);return this.dom_to_coords(d)},this));c=d.sort_by_row_and_col_asc(c),c.each(a.proxy(function(c,d){var e=a(d.el),f=e.coords().grid,g=parseInt(e.attr("data-col")),h={col:Math.max(Math.round(g+b),1),row:f.row,size_x:f.size_x,size_y:f.size_y};setTimeout(a.proxy(function(){this.mutate_widget_in_gridmap(e,f,h)},this),0)},this))},h.resize_widget_dimensions=function(b){return b.widget_margins&&(this.options.widget_margins=b.widget_margins),b.widget_base_dimensions&&(this.options.widget_base_dimensions=b.widget_base_dimensions),this.min_widget_width=2*this.options.widget_margins[0]+this.options.widget_base_dimensions[0],this.min_widget_height=2*this.options.widget_margins[1]+this.options.widget_base_dimensions[1],this.$widgets.each(a.proxy(function(b,c){var d=a(c);this.resize_widget(d)},this)),this.generate_grid_and_stylesheet(),this.get_widgets_from_DOM(),this.set_dom_grid_height(),this},h.mutate_widget_in_gridmap=function(b,c,d){var e=c.size_y,f=this.get_cells_occupied(c),g=this.get_cells_occupied(d),h=[];a.each(f.cols,function(b,c){-1===a.inArray(c,g.cols)&&h.push(c)});var i=[];a.each(g.cols,function(b,c){-1===a.inArray(c,f.cols)&&i.push(c)});var j=[];a.each(f.rows,function(b,c){-1===a.inArray(c,g.rows)&&j.push(c)});var k=[];if(a.each(g.rows,function(b,c){-1===a.inArray(c,f.rows)&&k.push(c)}),this.remove_from_gridmap(c),i.length){var l=[d.col,d.row,d.size_x,Math.min(e,d.size_y),b];this.empty_cells.apply(this,l)}if(k.length){var m=[d.col,d.row,d.size_x,d.size_y,b];this.empty_cells.apply(this,m)}if(c.col=d.col,c.row=d.row,c.size_x=d.size_x,c.size_y=d.size_y,this.add_to_gridmap(d,b),b.removeClass("player-revert"),this.update_widget_dimensions(b,d),h.length){var n=[h[0],d.row,h[h.length-1]-h[0]+1,Math.min(e,d.size_y),b];this.remove_empty_cells.apply(this,n)}if(j.length){var o=[d.col,d.row,d.size_x,d.size_y,b];this.remove_empty_cells.apply(this,o)}return this.move_widget_up(b),this},h.empty_cells=function(b,c,d,e,f){var g=this.widgets_below({col:b,row:c-e,size_x:d,size_y:e});return g.not(f).each(a.proxy(function(b,d){var f=a(d),g=f.coords().grid;if(g.row<=c+e-1){var h=c+e-g.row;this.move_widget_down(f,h)}},this)),this.set_dom_grid_height(),this},h.remove_empty_cells=function(b,c,d,e,f){var g=this.widgets_below({col:b,row:c,size_x:d,size_y:e});return g.not(f).each(a.proxy(function(b,c){this.move_widget_up(a(c),e)},this)),this.set_dom_grid_height(),this},h.next_position=function(a,b){a||(a=1),b||(b=1);for(var c,e=this.gridmap,f=e.length,g=[],h=1;f>h;h++){c=e[h].length;for(var i=1;c>=i;i++){var j=this.can_move_to({size_x:a,size_y:b},h,i);j&&g.push({col:h,row:i,size_y:b,size_x:a})}}return g.length?d.sort_by_row_and_col_asc(g)[0]:!1},h.remove_by_grid=function(a,b){var c=this.is_widget(a,b);c&&this.remove_widget(c)},h.remove_widget=function(b,c,d){var e=b instanceof a?b:a(b);if(0===e.length)return this;var f=e.coords().grid;if(void 0===f)return this;a.isFunction(c)&&(d=c,c=!1),this.cells_occupied_by_placeholder={},this.$widgets=this.$widgets.not(e);var g=this.widgets_below(e);return this.remove_from_gridmap(f),this.options.hide_element.call(this,e,a.proxy(function(){e.remove(),c||g.each(a.proxy(function(b,c){this.move_widget_up(a(c),f.size_y)},this)),this.set_dom_grid_height(),d&&d.call(this,b)},this)),this},h.remove_all_widgets=function(b){return this.$widgets.each(a.proxy(function(a,c){this.remove_widget(c,!0,b)},this)),this},h.serialize=function(b){b||(b=this.$widgets);var c=[];return b.each(a.proxy(function(b,d){var e=a(d);"undefined"!=typeof e.coords().grid&&c.push(this.options.serialize_params(e,e.coords().grid))},this)),c},h.serialize_changed=function(){return this.serialize(this.$changed)},h.dom_to_coords=function(a){return{col:parseInt(a.attr("data-col"),10),row:parseInt(a.attr("data-row"),10),size_x:parseInt(a.attr("data-sizex"),10)||1,size_y:parseInt(a.attr("data-sizey"),10)||1,max_size_x:parseInt(a.attr("data-max-sizex"),10)||!1,max_size_y:parseInt(a.attr("data-max-sizey"),10)||!1,min_size_x:parseInt(a.attr("data-min-sizex"),10)||!1,min_size_y:parseInt(a.attr("data-min-sizey"),10)||!1,el:a}},h.register_widget=function(b){var c=b instanceof a,d=c?this.dom_to_coords(b):b,e=!1;c||(b=d.el);var f=this.can_go_widget_up(d);return this.options.shift_widgets_up&&f&&(d.row=f,b.attr("data-row",f),this.$el.trigger("gridster:positionchanged",[d]),e=!0),this.options.avoid_overlapped_widgets&&!this.can_move_to({size_x:d.size_x,size_y:d.size_y},d.col,d.row)&&(a.extend(d,this.next_position(d.size_x,d.size_y)),b.attr({"data-col":d.col,"data-row":d.row,"data-sizex":d.size_x,"data-sizey":d.size_y}),e=!0),b.data("coords",b.coords()),b.data("coords").grid=d,this.add_to_gridmap(d,b),this.update_widget_dimensions(b,d),this.options.resize.enabled&&this.add_resize_handle(b),e},h.update_widget_position=function(a,b){return this.for_each_cell_occupied(a,function(a,c){return this.gridmap[a]?void(this.gridmap[a][c]=b):this}),this},h.update_widget_dimensions=function(a,b){var c=b.size_x*(this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0])+(b.size_x-1)*this.options.widget_margins[0],d=b.size_y*this.options.widget_base_dimensions[1]+(b.size_y-1)*this.options.widget_margins[1];return a.data("coords").update({width:c,height:d}),a.attr({"data-col":b.col,"data-row":b.row,"data-sizex":b.size_x,"data-sizey":b.size_y}),this},h.update_widgets_dimensions=function(){return a.each(this.$widgets,a.proxy(function(b,c){var d=a(c).coords().grid;"object"==typeof d&&this.update_widget_dimensions(a(c),d)},this)),this},h.remove_from_gridmap=function(a){return this.update_widget_position(a,!1)},h.add_to_gridmap=function(a,b){this.update_widget_position(a,b||a.el)},h.draggable=function(){var b=this,c=a.extend(!0,{},this.options.draggable,{offset_left:this.options.widget_margins[0],offset_top:this.options.widget_margins[1],container_width:this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0],limit:!0,start:function(c,d){b.$widgets.filter(".player-revert").removeClass("player-revert"),b.$player=a(this),b.$helper=a(d.$helper),b.helper=!b.$helper.is(b.$player),b.on_start_drag.call(b,c,d),b.$el.trigger("gridster:dragstart")},stop:function(a,c){b.on_stop_drag.call(b,a,c),b.$el.trigger("gridster:dragstop")},drag:throttle(function(a,c){b.on_drag.call(b,a,c),b.$el.trigger("gridster:drag")},60)});this.drag_api=this.$el.dragg(c).data("drag")},h.resizable=function(){return this.resize_api=this.$el.gridDraggable({items:"."+this.options.resize.handle_class,offset_left:this.options.widget_margins[0],container_width:this.container_width,move_element:!1,resize:!0,limit:this.options.max_cols!==1/0,scroll_container:this.options.scroll_container,start:a.proxy(this.on_start_resize,this),stop:a.proxy(function(b,c){delay(a.proxy(function(){this.on_stop_resize(b,c)},this),120)},this),drag:throttle(a.proxy(this.on_resize,this),60)}),this},h.setup_resize=function(){this.resize_handle_class=this.options.resize.handle_class;var b=this.options.resize.axes,c='';return this.resize_handle_tpl=a.map(b,function(a){return c.replace("{type}",a)}).join(""),a.isArray(this.options.draggable.ignore_dragging)&&this.options.draggable.ignore_dragging.push("."+this.resize_handle_class),this},h.on_start_drag=function(b,c){this.$helper.add(this.$player).add(this.$wrapper).addClass("dragging"),this.highest_col=this.get_highest_occupied_cell().col,this.$player.addClass("player"),this.player_grid_data=this.$player.coords().grid,this.placeholder_grid_data=a.extend({},this.player_grid_data),this.set_dom_grid_height(this.$el.height()+this.player_grid_data.size_y*this.min_widget_height),this.set_dom_grid_width(this.cols);var d=this.player_grid_data.size_x,e=this.cols-this.highest_col;this.options.max_cols===1/0&&d>=e&&this.add_faux_cols(Math.min(d-e,1));var f=this.faux_grid,g=this.$player.data("coords").coords;this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data),this.last_cols=[],this.last_rows=[],this.collision_api=this.$helper.collision(f,this.options.collision),this.$preview_holder=a("<"+this.$player.get(0).tagName+" />",{"class":"preview-holder","data-row":this.$player.attr("data-row"),"data-col":this.$player.attr("data-col"),css:{width:g.width,height:g.height}}).appendTo(this.$el),this.options.draggable.start&&this.options.draggable.start.call(this,b,c)},h.on_drag=function(a,b){if(null===this.$player)return!1;var c=this.options.widget_margins[0],d=this.$preview_holder.attr("data-col"),e={left:b.position.left+this.baseX-c*d,top:b.position.top+this.baseY};if(this.options.max_cols===1/0){var f=this.placeholder_grid_data.col+this.placeholder_grid_data.size_x-1;f>=this.cols-1&&this.options.max_cols>=this.cols+1&&(this.add_faux_cols(1),this.set_dom_grid_width(this.cols+1),this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0])),this.collision_api.set_colliders(this.faux_grid)}this.colliders_data=this.collision_api.get_closest_colliders(e),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.helper&&this.$player&&this.$player.css({left:b.position.left,top:b.position.top}),this.options.draggable.drag&&this.options.draggable.drag.call(this,a,b)},h.on_stop_drag=function(a,b){this.$helper.add(this.$player).add(this.$wrapper).removeClass("dragging");var c=this.options.widget_margins[0],d=this.$preview_holder.attr("data-col");b.position.left=b.position.left+this.baseX-c*d,b.position.top=b.position.top+this.baseY,this.colliders_data=this.collision_api.get_closest_colliders(b.position),this.on_overlapped_column_change(this.on_start_overlapping_column,this.on_stop_overlapping_column),this.on_overlapped_row_change(this.on_start_overlapping_row,this.on_stop_overlapping_row),this.$changed=this.$changed.add(this.$player),this.options.collision.wait_for_mouseup&&this.for_each_cell_occupied(this.placeholder_grid_data,function(a,b){this.is_widget(a,b)&&this.move_widget_down(this.is_widget(a,b),this.placeholder_grid_data.size_y)}),this.cells_occupied_by_player=this.get_cells_occupied(this.placeholder_grid_data);var e=this.placeholder_grid_data.col,f=this.placeholder_grid_data.row;this.set_cells_player_occupies(e,f),this.$player.coords().grid.row=f,this.$player.coords().grid.col=e,this.options.draggable.stop&&this.options.draggable.stop.call(this,a,b),this.$player.addClass("player-revert").removeClass("player").attr({"data-col":e,"data-row":f}).css({left:"",top:""}),this.$preview_holder.remove(),this.$player=null,this.$helper=null,this.placeholder_grid_data={},this.player_grid_data={},this.cells_occupied_by_placeholder={},this.cells_occupied_by_player={},this.w_queue={},this.set_dom_grid_height(),this.set_dom_grid_width(),this.options.max_cols===1/0&&this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0])},h.on_start_resize=function(b,c){this.$resized_widget=c.$player.closest(".gs-w"),this.resize_coords=this.$resized_widget.coords(),this.resize_wgd=this.resize_coords.grid,this.resize_initial_width=this.resize_coords.coords.width,this.resize_initial_height=this.resize_coords.coords.height,this.resize_initial_sizex=this.resize_coords.grid.size_x,this.resize_initial_sizey=this.resize_coords.grid.size_y,this.resize_initial_col=this.resize_coords.grid.col,this.resize_last_sizex=this.resize_initial_sizex,this.resize_last_sizey=this.resize_initial_sizey,this.resize_max_size_x=Math.min(this.resize_wgd.max_size_x||this.options.resize.max_size[0],this.options.max_cols-this.resize_initial_col+1),this.resize_max_size_y=this.resize_wgd.max_size_y||this.options.resize.max_size[1],this.resize_min_size_x=this.resize_wgd.min_size_x||this.options.resize.min_size[0]||1,this.resize_min_size_y=this.resize_wgd.min_size_y||this.options.resize.min_size[1]||1,this.resize_initial_last_col=this.get_highest_occupied_cell().col,this.set_dom_grid_width(this.cols),this.resize_dir={right:c.$player.is("."+this.resize_handle_class+"-x"),bottom:c.$player.is("."+this.resize_handle_class+"-y")},this.is_responsive()||this.$resized_widget.css({"min-width":this.options.widget_base_dimensions[0],"min-height":this.options.widget_base_dimensions[1]});var d=this.$resized_widget.get(0).tagName;this.$resize_preview_holder=a("<"+d+" />",{"class":"preview-holder resize-preview-holder","data-row":this.$resized_widget.attr("data-row"), +"data-col":this.$resized_widget.attr("data-col"),css:{width:this.resize_initial_width,height:this.resize_initial_height}}).appendTo(this.$el),this.$resized_widget.addClass("resizing"),this.options.resize.start&&this.options.resize.start.call(this,b,c,this.$resized_widget),this.$el.trigger("gridster:resizestart")},h.on_stop_resize=function(b,c){this.$resized_widget.removeClass("resizing").css({width:"",height:"","min-width":"","min-height":""}),delay(a.proxy(function(){this.$resize_preview_holder.remove().css({"min-width":"","min-height":""}),this.options.resize.stop&&this.options.resize.stop.call(this,b,c,this.$resized_widget),this.$el.trigger("gridster:resizestop")},this),300),this.set_dom_grid_width(),this.set_dom_grid_height(),this.options.max_cols===1/0&&this.drag_api.set_limits(this.cols*this.min_widget_width)},h.on_resize=function(a,b){var c,d=b.pointer.diff_left,e=b.pointer.diff_top,f=this.is_responsive()?this.get_responsive_col_width():this.options.widget_base_dimensions[0],g=this.options.widget_base_dimensions[1],h=this.options.widget_margins[0],i=this.options.widget_margins[1],j=this.resize_max_size_x,k=this.resize_min_size_x,l=this.resize_max_size_y,m=this.resize_min_size_y,n=this.options.max_cols===1/0,o=Math.ceil(d/(f+2*h)-.2),p=Math.ceil(e/(g+2*i)-.2),q=Math.max(1,this.resize_initial_sizex+o),r=Math.max(1,this.resize_initial_sizey+p),s=Math.floor(this.container_width/this.min_widget_width-this.resize_initial_col+1),t=s*this.min_widget_width+(s-1)*h;q=Math.max(Math.min(q,j),k),q=Math.min(s,q),c=j*f+(q-1)*h;var u=Math.min(c,t),v=k*f+(q-1)*h;r=Math.max(Math.min(r,l),m);var w=l*g+(r-1)*i,x=m*g+(r-1)*i;if(this.resize_dir.right?r=this.resize_initial_sizey:this.resize_dir.bottom&&(q=this.resize_initial_sizex),n){var y=this.resize_initial_col+q-1;n&&this.resize_initial_last_col<=y&&(this.set_dom_grid_width(Math.max(y+1,this.cols)),this.colsd;d++)-1===a.inArray(e[d],this.last_cols)&&(b||a.noop).call(this,e[d]);for(d=0;f>d;d++)-1===a.inArray(this.last_cols[d],e)&&(c||a.noop).call(this,this.last_cols[d]);return this.last_cols=e,this},h.on_overlapped_row_change=function(b,c){if(!this.colliders_data.length)return this;var d,e=this.get_targeted_rows(this.colliders_data[0].el.data.row),f=this.last_rows.length,g=e.length;for(d=0;g>d;d++)-1===a.inArray(e[d],this.last_rows)&&(b||a.noop).call(this,e[d]);for(d=0;f>d;d++)-1===a.inArray(this.last_rows[d],e)&&(c||a.noop).call(this,this.last_rows[d]);this.last_rows=e},h.set_player=function(b,c,d){var e=this,f=!1;d||this.empty_cells_player_occupies();var g=d?{col:b}:e.colliders_data[0].el.data,h=g.col,i=g.row||c;this.player_grid_data={col:h,row:i,size_y:this.player_grid_data.size_y,size_x:this.player_grid_data.size_x},this.cells_occupied_by_player=this.get_cells_occupied(this.player_grid_data),this.cells_occupied_by_placeholder=this.get_cells_occupied(this.placeholder_grid_data);var j=this.get_widgets_overlapped(this.player_grid_data),k=this.player_grid_data.size_y,l=this.player_grid_data.size_x,m=this.cells_occupied_by_placeholder,n=this;if(j.each(a.proxy(function(b,c){var d=a(c),e=d.coords().grid,g=m.cols[0]+l-1,o=m.rows[0]+k-1;if(d.hasClass(n.options.static_class))return!0;if(n.options.collision.wait_for_mouseup&&n.drag_api.is_dragging)n.placeholder_grid_data.col=h,n.placeholder_grid_data.row=i,n.cells_occupied_by_placeholder=n.get_cells_occupied(n.placeholder_grid_data),n.$preview_holder.attr({"data-row":i,"data-col":h});else if(e.size_x<=l&&e.size_y<=k)if(n.is_swap_occupied(m.cols[0],e.row,e.size_x,e.size_y)||n.is_player_in(m.cols[0],e.row)||n.is_in_queue(m.cols[0],e.row,d))if(n.is_swap_occupied(g,e.row,e.size_x,e.size_y)||n.is_player_in(g,e.row)||n.is_in_queue(g,e.row,d))if(n.is_swap_occupied(e.col,m.rows[0],e.size_x,e.size_y)||n.is_player_in(e.col,m.rows[0])||n.is_in_queue(e.col,m.rows[0],d))if(n.is_swap_occupied(e.col,o,e.size_x,e.size_y)||n.is_player_in(e.col,o)||n.is_in_queue(e.col,o,d))if(n.is_swap_occupied(m.cols[0],m.rows[0],e.size_x,e.size_y)||n.is_player_in(m.cols[0],m.rows[0])||n.is_in_queue(m.cols[0],m.rows[0],d))for(var p=0;l>p;p++)for(var q=0;k>q;q++){var r=m.cols[0]+p,s=m.rows[0]+q;if(!n.is_swap_occupied(r,s,e.size_x,e.size_y)&&!n.is_player_in(r,s)&&!n.is_in_queue(r,s,d)){f=n.queue_widget(r,s,d),p=l;break}}else f=n.queue_widget(m.cols[0],m.rows[0],d);else f=n.queue_widget(e.col,o,d);else f=n.queue_widget(e.col,m.rows[0],d);else f=n.queue_widget(g,e.row,d);else f=n.queue_widget(m.cols[0],e.row,d);else n.options.shift_larger_widgets_down&&!f&&j.each(a.proxy(function(b,c){var d=a(c);n.can_go_down(d)&&d.coords().grid.row===n.player_grid_data.row&&(n.move_widget_down(d,n.player_grid_data.size_y),n.set_placeholder(h,i))}));n.clean_up_changed()})),f&&this.can_placeholder_be_set(h,i,l,k)){for(var o in this.w_queue){var p=parseInt(o.split("_")[0]),q=parseInt(o.split("_")[1]);"full"!==this.w_queue[o]&&this.new_move_widget_to(this.w_queue[o],p,q)}this.set_placeholder(h,i)}if(!j.length){if(this.options.shift_widgets_up){var r=this.can_go_player_up(this.player_grid_data);r!==!1&&(i=r)}this.can_placeholder_be_set(h,i,l,k)&&this.set_placeholder(h,i)}return this.w_queue={},{col:h,row:i}},h.is_swap_occupied=function(a,b,c,d){for(var e=!1,f=0;c>f;f++)for(var g=0;d>g;g++){var h=a+f,i=b+g,j=h+"_"+i;if(this.is_occupied(h,i))e=!0;else if(j in this.w_queue){if("full"===this.w_queue[j]){e=!0;continue}var k=this.w_queue[j],l=k.coords().grid;this.is_widget_under_player(l.col,l.row)||delete this.w_queue[j]}i>parseInt(this.options.max_rows)&&(e=!0),h>parseInt(this.options.max_cols)&&(e=!0),this.is_player_in(h,i)&&(e=!0)}return e},h.can_placeholder_be_set=function(a,b,c,d){for(var e=!0,f=0;c>f;f++)for(var g=0;d>g;g++){var h=a+f,i=b+g,j=this.is_widget(h,i);i>parseInt(this.options.max_rows)&&(e=!1),h>parseInt(this.options.max_cols)&&(e=!1),this.is_occupied(h,i)&&!this.is_widget_queued_and_can_move(j)&&(e=!1)}return e},h.queue_widget=function(a,b,c){var d=c,e=d.coords().grid,f=a+"_"+b;if(f in this.w_queue)return!1;this.w_queue[f]=d;for(var g=0;g=0&&a.inArray(c,d.rows)>=0},h.is_placeholder_in=function(b,c){var d=this.cells_occupied_by_placeholder||{};return this.is_placeholder_in_col(b)&&a.inArray(c,d.rows)>=0},h.is_placeholder_in_col=function(b){var c=this.cells_occupied_by_placeholder||[];return a.inArray(b,c.cols)>=0},h.is_empty=function(a,b){return"undefined"!=typeof this.gridmap[a]?"undefined"!=typeof this.gridmap[a][b]&&this.gridmap[a][b]===!1?!0:!1:!0},h.is_valid_col=function(a,b){return this.options.max_cols===1/0?!0:this.cols>=this.calculate_highest_col(a,b)},h.is_valid_row=function(a,b){return this.rows>=this.calculate_highest_row(a,b)},h.calculate_highest_col=function(a,b){return a+(b||1)-1},h.calculate_highest_row=function(a,b){return a+(b||1)-1},h.is_occupied=function(a,b){return this.gridmap[a]&&this.gridmap[a][b]?!0:!1},h.is_widget=function(a,b){var c=this.gridmap[a];return c?(c=c[b],c?c:!1):!1},h.is_static=function(a,b){var c=this.gridmap[a];return c?(c=c[b],c&&c.hasClass(this.options.static_class)?!0:!1):!1},h.is_widget_under_player=function(a,b){return this.is_widget(a,b)?this.is_player_in(a,b):!1},h.get_widgets_under_player=function(b){b||(b=this.cells_occupied_by_player||{cols:[],rows:[]});var c=a([]);return a.each(b.cols,a.proxy(function(d,e){a.each(b.rows,a.proxy(function(a,b){this.is_widget(e,b)&&(c=c.add(this.gridmap[e][b]))},this))},this)),c},h.set_placeholder=function(b,c){var d=a.extend({},this.placeholder_grid_data),e=b+d.size_x-1;e>this.cols&&(b-=e-b);var f=this.placeholder_grid_data.row0&&(this.is_empty(a,h)||this.is_player(a,h)||this.is_widget(a,h)&&g[h].is(f));)d[a].push(h),e=e>h?h:e;return 0===d[a].length?(c=!1,!0):void d[a].sort(function(a,b){return a-b})}),c?this.get_valid_rows(a,d,e):!1},h.can_go_widget_up=function(a){var b=a.row+a.size_y-1,c=!0,d=[],e=1e4;return this.for_each_column_occupied(a,function(f){var g=this.gridmap[f];d[f]=[];for(var h=b+1;--h>0&&(!this.is_widget(f,h)||this.is_player_in(f,h)||g[h].is(a.el));)this.is_player(f,h)||this.is_placeholder_in(f,h)||this.is_player_in(f,h)||d[f].push(h),e>h&&(e=h);return 0===d[f].length?(c=!1,!0):void d[f].sort(function(a,b){return a-b})}),c?this.get_valid_rows(a,d,e):!1},h.get_valid_rows=function(b,c,d){for(var e=b.row,f=b.row+b.size_y-1,g=b.size_y,h=d-1,i=[];++h<=f;){var j=!0;if(a.each(c,function(b,c){a.isArray(c)&&-1===a.inArray(h,c)&&(j=!1)}),j===!0&&(i.push(h),i.length===g))break}var k=!1;return 1===g?i[0]!==e&&(k=i[0]||!1):i[0]!==e&&(k=this.get_consecutive_numbers_index(i,g)),k},h.get_consecutive_numbers_index=function(a,b){for(var c=a.length,d=[],e=!0,f=-1,g=0;c>g;g++){if(e||a[g]===f+1){if(d.push(g),d.length===b)break;e=!1}else d=[],e=!0;f=a[g]}return d.length>=b?a[d[0]]:!1},h.get_widgets_overlapped=function(){var b=a([]),c=[],d=this.cells_occupied_by_player.rows.slice(0);return d.reverse(),a.each(this.cells_occupied_by_player.cols,a.proxy(function(e,f){a.each(d,a.proxy(function(d,e){if(!this.gridmap[f])return!0;var g=this.gridmap[f][e];this.is_occupied(f,e)&&!this.is_player(g)&&-1===a.inArray(g,c)&&(b=b.add(g),c.push(g))},this))},this)),b},h.on_start_overlapping_column=function(a){this.set_player(a,void 0,!1)},h.on_start_overlapping_row=function(a){this.set_player(void 0,a,!1)},h.on_stop_overlapping_column=function(a){var b=this;this.options.shift_larger_widgets_down&&this.for_each_widget_below(a,this.cells_occupied_by_player.rows[0],function(a,c){b.move_widget_up(this,b.player_grid_data.size_y)})},h.on_stop_overlapping_row=function(a){var b=this,c=this.cells_occupied_by_player.cols;if(this.options.shift_larger_widgets_down)for(var d=0,e=c.length;e>d;d++)this.for_each_widget_below(c[d],a,function(a,c){b.move_widget_up(this,b.player_grid_data.size_y)})},h.new_move_widget_to=function(a,b,c){var d=a.coords().grid;return this.remove_from_gridmap(d),d.row=c,d.col=b,this.add_to_gridmap(d),a.attr("data-row",c),a.attr("data-col",b),this.update_widget_position(d,a),this.$changed=this.$changed.add(a),this},h.move_widget=function(a,b,c,d){var e=a.coords().grid,f={col:b,row:c,size_x:e.size_x,size_y:e.size_y};return this.mutate_widget_in_gridmap(a,e,f),this.set_dom_grid_height(),this.set_dom_grid_width(),d&&d.call(this,f.col,f.row),a},h.move_widget_to=function(b,c){var d=this,e=b.coords().grid,f=this.widgets_below(b),g=this.can_move_to(e,e.col,c);return g===!1?!1:(this.remove_from_gridmap(e),e.row=c,this.add_to_gridmap(e),b.attr("data-row",c),this.$changed=this.$changed.add(b),f.each(function(b,c){var e=a(c),f=e.coords().grid,g=d.can_go_widget_up(f);g&&g!==f.row&&d.move_widget_to(e,g)}),this)},h.move_widget_up=function(b,c){if(void 0===c)return!1;var d=b.coords().grid,e=d.row,f=[];return c||(c=1),this.can_go_up(b)?void this.for_each_column_occupied(d,function(d){if(-1===a.inArray(b,f)){var g=b.coords().grid,h=e-c;if(h=this.can_go_up_to_row(g,d,h),!h)return!0;this.remove_from_gridmap(g),g.row=h,this.add_to_gridmap(g),b.attr("data-row",g.row),this.$changed=this.$changed.add(b),f.push(b)}}):!1},h.move_widget_down=function(b,c){var d,e,f,g;if(0>=c)return!1;if(d=b.coords().grid,e=d.row,f=[],g=c,!b)return!1;if(-1===a.inArray(b,f)){var h=b.coords().grid,i=e+c,j=this.widgets_below(b);this.remove_from_gridmap(h),j.each(a.proxy(function(b,c){var d=a(c),e=d.coords().grid,f=this.displacement_diff(e,h,g);f>0&&this.move_widget_down(d,f)},this)),h.row=i,this.update_widget_position(h,b),b.attr("data-row",h.row),this.$changed=this.$changed.add(b),f.push(b)}},h.can_go_up_to_row=function(b,c,d){var e,f=!0,g=[],h=b.row;if(this.for_each_column_occupied(b,function(a){for(g[a]=[],e=h;e--&&this.is_empty(a,e)&&!this.is_placeholder_in(a,e);)g[a].push(e);return g[a].length?void 0:(f=!1,!0)}),!f)return!1;for(e=d,e=1;h>e;e++){for(var i=!0,j=0,k=g.length;k>j;j++)g[j]&&-1===a.inArray(e,g[j])&&(i=!1);if(i===!0){f=e;break}}return f},h.displacement_diff=function(a,b,c){var d=a.row,e=[],f=b.row+b.size_y;this.for_each_column_occupied(a,function(a){for(var b=0,c=f;d>c;c++)this.is_empty(a,c)&&(b+=1);e.push(b)});var g=Math.max.apply(Math,e);return c-=g,c>0?c:0},h.widgets_below=function(b){var c=a([]),e=a.isPlainObject(b)?b:b.coords().grid;if(void 0===e)return c;var f=this,g=e.row+e.size_y-1;return this.for_each_column_occupied(e,function(b){f.for_each_widget_below(b,g,function(b,d){return f.is_player(this)||-1!==a.inArray(this,c)?void 0:(c=c.add(this),!0)})}),d.sort_by_row_asc(c)},h.set_cells_player_occupies=function(a,b){return this.remove_from_gridmap(this.placeholder_grid_data),this.placeholder_grid_data.col=a,this.placeholder_grid_data.row=b,this.add_to_gridmap(this.placeholder_grid_data,this.$player),this},h.empty_cells_player_occupies=function(){return this.remove_from_gridmap(this.placeholder_grid_data),this},h.can_go_down=function(b){var c=!0,d=this;return b.hasClass(this.options.static_class)&&(c=!1),this.widgets_below(b).each(function(){a(this).hasClass(d.options.static_class)&&(c=!1)}),c},h.can_go_up=function(a){var b=a.coords().grid,c=b.row,d=c-1,e=!0;return 1===c?!1:(this.for_each_column_occupied(b,function(a){return this.is_occupied(a,d)||this.is_player(a,d)||this.is_placeholder_in(a,d)||this.is_player_in(a,d)?(e=!1,!0):void 0}),e)},h.can_move_to=function(a,b,c){var d=a.el,e={size_y:a.size_y,size_x:a.size_x,col:b,row:c},f=!0;if(this.options.max_cols!==1/0){var g=b+a.size_x-1;if(g>this.cols)return!1}return this.options.max_rows=d;d++)c.push(d);return c},h.get_targeted_rows=function(a){for(var b=(a||this.player_grid_data.row)+(this.player_grid_data.size_y-1),c=[],d=a;b>=d;d++)c.push(d);return c},h.get_cells_occupied=function(b){var c,d={cols:[],rows:[]};for(arguments[1]instanceof a&&(b=arguments[1].coords().grid),c=0;c0&&this.is_widget(d,m)&&-1===a.inArray(g[d][m],l)&&(h=f.call(g[d][m],d,m),l.push(g[d][m]),h)););},"for_each/below":function(){for(m=e+1,i=g[d].length;i>m;m++)this.is_widget(d,m)&&-1===a.inArray(g[d][m],l)&&(h=f.call(g[d][m],d,m),l.push(g[d][m]))}};n[j]&&n[j].call(this)}},h.for_each_widget_above=function(a,b,c){return this._traversing_widgets("for_each","above",a,b,c),this},h.for_each_widget_below=function(a,b,c){return this._traversing_widgets("for_each","below",a,b,c),this},h.get_highest_occupied_cell=function(){for(var a,b=this.gridmap,c=b[1].length,d=[],e=[],f=b.length-1;f>=1;f--)for(a=c-1;a>=1;a--)if(this.is_widget(f,a)){d.push(a),e.push(f);break}return{col:Math.max.apply(Math,e),row:Math.max.apply(Math,d)}},h.get_widgets_in_range=function(b,c,d,e){var f,g,h,i,j=a([]);for(f=d;f>=b;f--)for(g=e;g>=c;g--)h=this.is_widget(f,g),h!==!1&&(i=h.data("coords").grid,i.col>=b&&i.col<=d&&i.row>=c&&i.row<=e&&(j=j.add(h)));return j},h.get_widgets_at_cell=function(a,b){return this.get_widgets_in_range(a,b,a,b)},h.get_widgets_from=function(b,c){var d=a();return b&&(d=d.add(this.$widgets.filter(function(){var c=parseInt(a(this).attr("data-col"));return c===b||c>b}))),c&&(d=d.add(this.$widgets.filter(function(){var b=parseInt(a(this).attr("data-row"));return b===c||b>c}))),d},h.set_dom_grid_height=function(a){if("undefined"==typeof a){var b=this.get_highest_occupied_cell().row;a=(b+1)*this.options.widget_margins[1]+b*this.min_widget_height}return this.container_height=a,this.$el.css("height",this.container_height),this},h.set_dom_grid_width=function(a){"undefined"==typeof a&&(a=this.get_highest_occupied_cell().col);var b=this.options.max_cols===1/0?this.options.max_cols:this.cols;return a=Math.min(b,Math.max(a,this.options.min_cols)),this.container_width=(a+1)*this.options.widget_margins[0]+a*this.min_widget_width,this.is_responsive()?(this.$el.css({"min-width":"100%","max-width":"100%"}),this):(this.$el.css("width",this.container_width),this)},h.is_responsive=function(){return this.options.autogenerate_stylesheet&&"auto"===this.options.widget_base_dimensions[0]&&this.options.max_cols!==1/0},h.get_responsive_col_width=function(){var a=this.cols||this.options.max_cols;return(this.$el[0].scrollWidth-(a+1)*this.options.widget_margins[0])/a},h.resize_responsive_layout=function(){return this.min_widget_width=this.get_responsive_col_width(),this.generate_stylesheet(),this.update_widgets_dimensions(),this.drag_api.set_limits(this.cols*this.min_widget_width+(this.cols+1)*this.options.widget_margins[0]),this},h.toggle_collapsed_grid=function(a,b){return a?(this.$widgets.css({"margin-top":b.widget_margins[0],"margin-bottom":b.widget_margins[0],"min-height":b.widget_base_dimensions[1]}),this.$el.addClass("collapsed"),this.resize_api&&this.disable_resize(),this.drag_api&&this.disable()):(this.$widgets.css({"margin-top":"auto","margin-bottom":"auto","min-height":"auto"}),this.$el.removeClass("collapsed"),this.resize_api&&this.enable_resize(),this.drag_api&&this.enable()),this},h.generate_stylesheet=function(b){var c,e="",f=this.is_responsive()&&this.options.responsive_breakpoint&&a(window).width()=0)return!1;for(this.generated_stylesheets.push(g),d.generated_stylesheets.push(g),c=1;c<=b.cols+1;c++)e+=b.namespace+' [data-col="'+c+'"] { left:'+(f?this.options.widget_margins[0]:c*b.widget_margins[0]+(c-1)*b.widget_base_dimensions[0])+"px; }\n";for(c=1;c<=b.rows+1;c++)e+=b.namespace+' [data-row="'+c+'"] { top:'+(c*b.widget_margins[1]+(c-1)*b.widget_base_dimensions[1])+"px; }\n";for(var h=1;h<=b.rows;h++)e+=b.namespace+' [data-sizey="'+h+'"] { height:'+(f?"auto":h*b.widget_base_dimensions[1]+(h-1)*b.widget_margins[1])+(f?"":"px")+"; }\n";for(var i=1;i<=b.cols;i++){var j=i*b.widget_base_dimensions[0]+(i-1)*b.widget_margins[0];e+=b.namespace+' [data-sizex="'+i+'"] { width:'+(f?this.$wrapper.width()-2*this.options.widget_margins[0]:j>this.$wrapper.width()?this.$wrapper.width():j)+"px; }\n"}return this.remove_style_tags(),this.add_style_tag(e)},h.add_style_tag=function(a){var b=document,c="gridster-stylesheet";if(""!==this.options.namespace&&(c=c+"-"+this.options.namespace),!document.getElementById(c)){var d=b.createElement("style");d.id=c,b.getElementsByTagName("head")[0].appendChild(d),d.setAttribute("type","text/css"),d.styleSheet?d.styleSheet.cssText=a:d.appendChild(document.createTextNode(a)),this.remove_style_tags(),this.$style_tags=this.$style_tags.add(d)}return this},h.remove_style_tags=function(){var b=d.generated_stylesheets,c=this.generated_stylesheets;this.$style_tags.remove(),d.generated_stylesheets=a.map(b,function(b){return-1===a.inArray(b,c)?b:void 0})},h.generate_faux_grid=function(a,b){this.faux_grid=[],this.gridmap=[];var c,d;for(c=b;c>0;c--)for(this.gridmap[c]=[],d=a;d>0;d--)this.add_faux_cell(d,c);return this},h.add_faux_cell=function(b,c){var d=a({left:this.baseX+(c-1)*this.min_widget_width,top:this.baseY+(b-1)*this.min_widget_height,width:this.min_widget_width,height:this.min_widget_height,col:c,row:b,original_col:c,original_row:b}).coords();return a.isArray(this.gridmap[c])||(this.gridmap[c]=[]),"undefined"==typeof this.gridmap[c][b]&&(this.gridmap[c][b]=!1),this.faux_grid.push(d),this},h.add_faux_rows=function(a){a=window.parseInt(a,10);for(var b=this.rows,c=b+parseInt(a||1),d=c;d>b;d--)for(var e=this.cols;e>=1;e--)this.add_faux_cell(d,e);return this.rows=c,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},h.add_faux_cols=function(a){a=window.parseInt(a,10);var b=this.cols,c=b+parseInt(a||1);c=Math.min(c,this.options.max_cols);for(var d=b+1;c>=d;d++)for(var e=this.rows;e>=1;e--)this.add_faux_cell(e,d);return this.cols=c,this.options.autogenerate_stylesheet&&this.generate_stylesheet(),this},h.recalculate_faux_grid=function(){var b=this.$wrapper.width();return this.baseX=(f.width()-b)/2,this.baseY=this.$wrapper.offset().top,"relative"===this.$wrapper.css("position")&&(this.baseX=this.baseY=0),a.each(this.faux_grid,a.proxy(function(a,b){this.faux_grid[a]=b.update({left:this.baseX+(b.data.col-1)*this.min_widget_width,top:this.baseY+(b.data.row-1)*this.min_widget_height})},this)),this.is_responsive()&&this.resize_responsive_layout(),this.options.center_widgets&&this.center_widgets(),this},h.resize_widget_dimensions=function(b){return b.widget_margins&&(this.options.widget_margins=b.widget_margins),b.widget_base_dimensions&&(this.options.widget_base_dimensions=b.widget_base_dimensions),this.$widgets.each(a.proxy(function(b,c){var d=a(c);this.resize_widget(d)},this)),this.generate_grid_and_stylesheet(),this.get_widgets_from_DOM(),this.set_dom_grid_height(),this.set_dom_grid_width(),this},h.get_widgets_from_DOM=function(){var b=this.$widgets.map(a.proxy(function(b,c){var d=a(c);return this.dom_to_coords(d)},this));b=d.sort_by_row_and_col_asc(b);var c=a(b).map(a.proxy(function(a,b){return this.register_widget(b)||null},this));return c.length&&this.$el.trigger("gridster:positionschanged"),this},h.get_num_widgets=function(){return this.$widgets.size()},h.set_num_columns=function(b){var c=this.options.max_cols,d=Math.floor(b/(this.min_widget_width+this.options.widget_margins[0]))+this.options.extra_cols,e=this.$widgets.map(function(){return a(this).attr("data-col")}).get();e.length||(e=[0]);var f=Math.max.apply(Math,e);this.cols=Math.max(f,d,this.options.min_cols),c!==1/0&&c>=f&&c=0;b--)if(this.is_widget(a,b)!==!1)return!0;return!1},c.widgets_in_row=function(a){for(var b=this.gridmap.length;b>=1;b--)if(this.is_widget(b,a)!==!1)return!0;return!1},c.get_bottom_most_occupied_cell=function(){var a=0,b=0;return this.for_each_cell(function(c,d,e){c&&e>a&&(a=e,b=d)}),{col:b,row:a}},c.get_right_most_occupied_cell=function(){var a=0,b=0;return this.for_each_cell(function(c,d,e){return c?(a=e,b=d,!1):void 0}),{col:b,row:a}},c.for_each_cell=function(a,b){b||(b=this.gridmap);var c=b.length,d=b[1].length;a:for(var e=c-1;e>=1;e--)for(var f=d-1;f>=1;f--){var g=b[e]&&b[e][f];if(a){if(a.call(this,g,e,f)===!1)break a}else;}},c.next_position_in_range=function(a,c){a||(a=1),c||(c=1);for(var d,e=this.gridmap,f=e.length,g=[],h=1;f>h;h++){d=this.options.max_rows||e[h].length;for(var i=1;d>=i;i++){var j=this.can_move_to({size_x:a,size_y:c},h,i);j&&g.push({col:h,row:i,size_y:c,size_x:a})}}return g.length>=1?b.sort_by_col_asc(g)[0]:!1},c.closest_to_right=function(a,b){if(!this.gridmap[a])return!1;for(var c=this.gridmap.length-1,d=a;c>=d;d++)if(this.gridmap[d][b])return{col:d,row:b};return!1},c.closest_to_left=function(a,b){if(!this.gridmap[a])return!1;for(var c=a;c>=1;c--)if(this.gridmap[c][b])return{col:c,row:b};return!1},b}); \ No newline at end of file diff --git a/lib/gridster/gridster.js-rails.gemspec b/lib/gridster/gridster.js-rails.gemspec new file mode 100644 index 000000000..c31e4d858 --- /dev/null +++ b/lib/gridster/gridster.js-rails.gemspec @@ -0,0 +1,21 @@ +# coding: utf-8 +require File.expand_path('../lib/gridster.js-rails/version', __FILE__) + +Gem::Specification.new do |spec| + spec.name = "gridster.js-rails" + spec.version = Gridster::Rails::VERSION + spec.authors = ["dsmorse"] + spec.email = ['https://github.com/dsmorse'] + + spec.summary = %q{jQuery plugin for draggable grid layouts} + spec.description = %q{Gridster is a jQuery plugin that makes building intuitive draggable layouts from elements spanning multiple columns. You can even dynamically add and remove elements from the grid.} + spec.homepage = "https://github.com/dsmorse/gridster.js" + spec.licenses = ['MIT'] + + spec.files = Dir["{demos,lib,vendor}/**/*"] + ["LICENSE", "bower.json", "package.json", "CHANGELOG.md", "README.md"] + + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.9" + spec.add_development_dependency "rake", "~> 10.0" +end diff --git a/lib/gridster/lib/gridster.js-rails.rb b/lib/gridster/lib/gridster.js-rails.rb new file mode 100644 index 000000000..d94c2b9a6 --- /dev/null +++ b/lib/gridster/lib/gridster.js-rails.rb @@ -0,0 +1,5 @@ +module Gridster + module Rails + require "gridster.js-rails/engine" + end +end diff --git a/lib/gridster/lib/gridster.js-rails/engine.rb b/lib/gridster/lib/gridster.js-rails/engine.rb new file mode 100644 index 000000000..3e0f585e7 --- /dev/null +++ b/lib/gridster/lib/gridster.js-rails/engine.rb @@ -0,0 +1,6 @@ +module Gridster + module Rails + class Engine < ::Rails::Engine + end + end +end diff --git a/lib/gridster/lib/gridster.js-rails/version.rb b/lib/gridster/lib/gridster.js-rails/version.rb new file mode 100644 index 000000000..bcf402894 --- /dev/null +++ b/lib/gridster/lib/gridster.js-rails/version.rb @@ -0,0 +1,5 @@ +module Gridster + module Rails + VERSION = "0.6.10" + end +end diff --git a/lib/gridster/package.json b/lib/gridster/package.json index 23354b5a3..063b74d5d 100644 --- a/lib/gridster/package.json +++ b/lib/gridster/package.json @@ -2,40 +2,57 @@ "name": "gridster", "title": "gridster.js", "description": "a drag-and-drop multi-column jQuery grid plugin", - "version": "0.5.6", - "homepage": "http://gridster.net/", + "version": "0.6.10", + "homepage": "https://dsmorse.github.io/gridster.js/", "author": { - "name": "ducksboard", - "email": "hackers@ducksboard.com" + "name": "ducksboard" }, "repository": { "type": "git", - "url": "git://github.com/ducksboard/gridster.js.git" + "url": "git://github.com/dsmorse/gridster.js.git" }, "bugs": { - "url": "https://github.com/ducksboard/gridster.js/issues" + "url": "https://github.com/dsmorse/gridster.js/issues" }, + "license": "MIT", "licenses": [ { "type": "MIT", - "url": "https://github.com/ducksboard/gridster.js/blob/master/LICENSE" + "url": "https://github.com/dsmorse/gridster.js/blob/master/LICENSE" } ], "keywords": [], - "dependencies": { - "jquery": "git+https://github.com/jquery/jquery.git#2.0.3" - }, "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-uglify": "~0.2.0", - "grunt-contrib-jshint": "~0.3.0", - "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-watch": "~0.3.1", - "grunt-contrib-cssmin": "~0.5.0", - "grunt-contrib-yuidoc": "~0.4.0", "bower": "~0.9.2", - "qunitjs": "~1.11.0", + "grunt": "~0.4.5", "grunt-bump": "0.0.11", - "grunt-conventional-changelog": "~1.0.0" + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-copy": "^0.8.0", + "grunt-contrib-cssmin": "~0.10.0", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-less": "^1.0.1", + "grunt-contrib-qunit": "^0.7.0", + "grunt-contrib-uglify": "~0.6.0", + "grunt-contrib-watch": "~0.6.1", + "grunt-contrib-yuidoc": "~0.7.0", + "grunt-conventional-changelog": "~1.0.0", + "grunt-gh-pages": "^0.10.0", + "grunt-shell": "^1.1.2", + "grunt-text-replace": "^0.4.0", + "jshint-stylish": "^1.0.1" + }, + "scripts": { + "test": "grunt test" + }, + "main": "dist/jquery.gridster.js", + "directories": { + "test": "test" + }, + "jspm": { + "main": "jquery.gridster", + "directories": { + "lib": "dist" + } } } diff --git a/lib/gridster/src/jquery.collision.js b/lib/gridster/src/jquery.collision.js index 4a15e8e34..c25ef8498 100644 --- a/lib/gridster/src/jquery.collision.js +++ b/lib/gridster/src/jquery.collision.js @@ -7,8 +7,11 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery')); + } + else if (typeof define === 'function' && define.amd) { define('gridster-collision', ['jquery', 'gridster-coords'], factory); } else { root.GridsterCollision = factory(root.$ || root.jQuery, @@ -16,7 +19,7 @@ } }(this, function($, Coords) { - + 'use strict'; var defaults = { colliders_context: document.body, overlapping_region: 'C' @@ -169,20 +172,22 @@ var area_coords = self.calculate_overlapped_area_coords( player_coords, collider_coords); var area = self.calculate_overlapped_area(area_coords); - var collider_data = { - area: area, - area_coords : area_coords, - region: region, - coords: collider_coords, - player_coords: player_coords, - el: $collider - }; + if ( 0 !== area ) { + var collider_data = { + area: area, + area_coords : area_coords, + region: region, + coords: collider_coords, + player_coords: player_coords, + el: $collider + }; - if (self.options.on_overlap) { - self.options.on_overlap.call(this, collider_data); + if (self.options.on_overlap) { + self.options.on_overlap.call(this, collider_data); + } + colliders_coords.push($collider_coords_ins); + colliders_data.push(collider_data); } - colliders_coords.push($collider_coords_ins); - colliders_data.push(collider_data); } } diff --git a/lib/gridster/src/jquery.coords.js b/lib/gridster/src/jquery.coords.js index 821b67c8b..c44d38c7f 100644 --- a/lib/gridster/src/jquery.coords.js +++ b/lib/gridster/src/jquery.coords.js @@ -7,14 +7,18 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery')); + } + else if (typeof define === 'function' && define.amd) { define('gridster-coords', ['jquery'], factory); } else { root.GridsterCoords = factory(root.$ || root.jQuery); } }(this, function($) { + 'use strict'; /** * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height) * to simulate DOM elements on the screen. @@ -55,8 +59,8 @@ if (el && !update) { this.data = el.offset(); - this.data.width = el.width(); - this.data.height = el.height(); + this.data.width = el[0].scrollWidth; + this.data.height = el[0].scrollHeight; } if (el && update && !not_update_offsets) { @@ -67,8 +71,13 @@ var d = this.data; - typeof d.left === 'undefined' && (d.left = d.x1); - typeof d.top === 'undefined' && (d.top = d.y1); + if ( d.left === undefined ) { + d.left = d.x1; + } + + if ( d.top === undefined ) { + d.top = d.y1; + } this.coords.x1 = d.left; this.coords.y1 = d.top; @@ -115,7 +124,7 @@ return this.data('coords'); } - var ins = new Coords(this, arguments[0]); + var ins = new Coords(this); this.data('coords', ins); return ins; }; diff --git a/lib/gridster/src/jquery.draggable.js b/lib/gridster/src/jquery.draggable.js index d9b5d9884..cab560439 100644 --- a/lib/gridster/src/jquery.draggable.js +++ b/lib/gridster/src/jquery.draggable.js @@ -7,15 +7,18 @@ */ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery')); + } + else if (typeof define === 'function' && define.amd) { define('gridster-draggable', ['jquery'], factory); } else { root.GridsterDraggable = factory(root.$ || root.jQuery); } }(this, function($) { - + 'use strict'; var defaults = { items: 'li', distance: 1, @@ -44,7 +47,7 @@ var idCounter = 0; var uniqId = function() { return ++idCounter + ''; - } + }; /** * Basic drag implementation for DOM elements inside a container. @@ -76,15 +79,16 @@ * @constructor */ function Draggable(el, options) { - this.options = $.extend({}, defaults, options); - this.$document = $(document); - this.$container = $(el); - this.$dragitems = $(this.options.items, this.$container); - this.is_dragging = false; - this.player_min_left = 0 + this.options.offset_left; - this.id = uniqId(); - this.ns = '.gridster-draggable-' + this.id; - this.init(); + this.options = $.extend({}, defaults, options); + this.$document = $(document); + this.$container = $(el); + this.$scroll_container = this.options.scroll_container === window ? + $(window) : this.$container.closest(this.options.scroll_container); + this.is_dragging = false; + this.player_min_left = 0 + this.options.offset_left; + this.id = uniqId(); + this.ns = '.gridster-draggable-' + this.id; + this.init(); } Draggable.defaults = defaults; @@ -98,8 +102,8 @@ this.disabled = false; this.events(); - $(window).bind(this.nsEvent('resize'), - throttle($.proxy(this.calculate_dimensions, this), 200)); + $window.bind(this.nsEvent('resize'), + throttle($.proxy(this.calculate_dimensions, this), 200)); }; fn.nsEvent = function(ev) { @@ -110,7 +114,7 @@ this.pointer_events = { start: this.nsEvent('touchstart') + ' ' + this.nsEvent('mousedown'), move: this.nsEvent('touchmove') + ' ' + this.nsEvent('mousemove'), - end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup'), + end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup') }; this.$container.on(this.nsEvent('selectstart'), @@ -130,8 +134,7 @@ }; fn.get_actual_pos = function($el) { - var pos = $el.position(); - return pos; + return $el.position(); }; @@ -156,9 +159,13 @@ var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top); var left = Math.round(this.el_init_offset.left + - diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x); + diff_x - this.baseX + + this.$scroll_container.scrollLeft() - + this.scroll_container_offset_x); var top = Math.round(this.el_init_offset.top + - diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y); + diff_y - this.baseY + + this.$scroll_container.scrollTop() - + this.scroll_container_offset_y); if (this.options.limit) { if (left > this.player_max_left) { @@ -176,8 +183,10 @@ pointer: { left: mouse_actual_pos.left, top: mouse_actual_pos.top, - diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x), - diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y) + diff_left: diff_x + (this.$scroll_container.scrollLeft() - + this.scroll_container_offset_x), + diff_top: diff_y + (this.$scroll_container.scrollTop() - + this.scroll_container_offset_y) } }; }; @@ -208,28 +217,36 @@ var area_size = 50; var scroll_inc = 30; + var scrollDir = 'scroll' + capitalize(dir_prop); var is_x = axis === 'x'; - var window_size = is_x ? this.window_width : this.window_height; - var doc_size = is_x ? $(document).width() : $(document).height(); + var scroller_size = is_x ? this.scroller_width : this.scroller_height; + var doc_size; + if (this.$scroll_container === window){ + doc_size = is_x ? this.$scroll_container.width() : + this.$scroll_container.height(); + }else{ + doc_size = is_x ? this.$scroll_container[0].scrollWidth : + this.$scroll_container[0].scrollHeight; + } var player_size = is_x ? this.$player.width() : this.$player.height(); var next_scroll; - var scroll_offset = $window['scroll' + capitalize(dir_prop)](); - var min_window_pos = scroll_offset; - var max_window_pos = min_window_pos + window_size; + var scroll_offset = this.$scroll_container[scrollDir](); + var min_scroll_pos = scroll_offset; + var max_scroll_pos = min_scroll_pos + scroller_size; - var mouse_next_zone = max_window_pos - area_size; // down/right - var mouse_prev_zone = min_window_pos + area_size; // up/left + var mouse_next_zone = max_scroll_pos - area_size; // down/right + var mouse_prev_zone = min_scroll_pos + area_size; // up/left - var abs_mouse_pos = min_window_pos + data.pointer[dir_prop]; + var abs_mouse_pos = min_scroll_pos + data.pointer[dir_prop]; - var max_player_pos = (doc_size - window_size + player_size); + var max_player_pos = (doc_size - scroller_size + player_size); if (abs_mouse_pos >= mouse_next_zone) { next_scroll = scroll_offset + scroll_inc; if (next_scroll < max_player_pos) { - $window['scroll' + capitalize(dir_prop)](next_scroll); + this.$scroll_container[scrollDir](next_scroll); this['scroll_offset_' + axis] += scroll_inc; } } @@ -237,7 +254,7 @@ if (abs_mouse_pos <= mouse_prev_zone) { next_scroll = scroll_offset - scroll_inc; if (next_scroll > 0) { - $window['scroll' + capitalize(dir_prop)](next_scroll); + this.$scroll_container[scrollDir](next_scroll); this['scroll_offset_' + axis] -= scroll_inc; } } @@ -252,14 +269,13 @@ }; - fn.calculate_dimensions = function(e) { - this.window_height = $window.height(); - this.window_width = $window.width(); + fn.calculate_dimensions = function() { + this.scroller_height = this.$scroll_container.height(); + this.scroller_width = this.$scroll_container.width(); }; fn.drag_handler = function(e) { - var node = e.target.nodeName; // skip if drag is disabled, or click was not done with the mouse primary button if (this.disabled || e.which !== 1 && !isTouch) { return; @@ -315,7 +331,6 @@ var offset = this.$container.offset(); this.baseX = Math.round(offset.left); this.baseY = Math.round(offset.top); - this.initial_container_width = this.options.container_width || this.$container.width(); if (this.options.helper === 'clone') { this.$helper = this.$player.clone() @@ -325,13 +340,10 @@ this.helper = false; } - this.win_offset_y = $(window).scrollTop(); - this.win_offset_x = $(window).scrollLeft(); - this.scroll_offset_y = 0; - this.scroll_offset_x = 0; + this.scroll_container_offset_y = this.$scroll_container.scrollTop(); + this.scroll_container_offset_x = this.$scroll_container.scrollLeft(); this.el_init_offset = this.$player.offset(); this.player_width = this.$player.width(); - this.player_height = this.$player.height(); this.set_limits(this.options.container_width); @@ -405,7 +417,7 @@ this.$container.off(this.ns); this.$document.off(this.ns); - $(window).off(this.ns); + $window.off(this.ns); $.removeData(this.$container, 'drag'); }; @@ -419,14 +431,26 @@ return this.options.ignore_dragging(event); } + if (this.options.resize) { + return ! $(event.target).is(this.options.items); + } + return $(event.target).is(this.options.ignore_dragging.join(', ')); }; //jQuery adapter - $.fn.drag = function ( options ) { + $.fn.gridDraggable = function ( options ) { return new Draggable(this, options); }; + $.fn.dragg = function (options) { + return this.each(function () { + if (!$.data(this, 'drag')) { + $.data(this, 'drag', new Draggable(this, options)); + } + }); + }; + return Draggable; })); diff --git a/lib/gridster/src/jquery.gridster.css b/lib/gridster/src/jquery.gridster.css deleted file mode 100644 index 52feaab18..000000000 --- a/lib/gridster/src/jquery.gridster.css +++ /dev/null @@ -1,117 +0,0 @@ -.gridster { - position:relative; -} - -.gridster > * { - margin: 0 auto; - -webkit-transition: height .4s, width .4s; - -moz-transition: height .4s, width .4s; - -o-transition: height .4s, width .4s; - -ms-transition: height .4s, width .4s; - transition: height .4s, width .4s; -} - -.gridster .gs-w { - z-index: 2; - position: absolute; -} - -.ready .gs-w:not(.preview-holder) { - -webkit-transition: opacity .3s, left .3s, top .3s; - -moz-transition: opacity .3s, left .3s, top .3s; - -o-transition: opacity .3s, left .3s, top .3s; - transition: opacity .3s, left .3s, top .3s; -} - -.ready .gs-w:not(.preview-holder), -.ready .resize-preview-holder { - -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; - -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; - -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; - transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; -} - -.gridster .preview-holder { - z-index: 1; - position: absolute; - background-color: #fff; - border-color: #fff; - opacity: 0.3; -} - -.gridster .player-revert { - z-index: 10!important; - -webkit-transition: left .3s, top .3s!important; - -moz-transition: left .3s, top .3s!important; - -o-transition: left .3s, top .3s!important; - transition: left .3s, top .3s!important; -} - -.gridster .dragging, -.gridster .resizing { - z-index: 10!important; - -webkit-transition: all 0s !important; - -moz-transition: all 0s !important; - -o-transition: all 0s !important; - transition: all 0s !important; -} - - -.gs-resize-handle { - position: absolute; - z-index: 1; -} - -.gs-resize-handle-both { - width: 20px; - height: 20px; - bottom: -8px; - right: -8px; - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4='); - background-position: top left; - background-repeat: no-repeat; - cursor: se-resize; - z-index: 20; -} - -.gs-resize-handle-x { - top: 0; - bottom: 13px; - right: -5px; - width: 10px; - cursor: e-resize; -} - -.gs-resize-handle-y { - left: 0; - right: 13px; - bottom: -5px; - height: 10px; - cursor: s-resize; -} - -.gs-w:hover .gs-resize-handle, -.resizing .gs-resize-handle { - opacity: 1; -} - -.gs-resize-handle, -.gs-w.dragging .gs-resize-handle { - opacity: 0; -} - -.gs-resize-disabled .gs-resize-handle { - display: none!important; -} - -[data-max-sizex="1"] .gs-resize-handle-x, -[data-max-sizey="1"] .gs-resize-handle-y, -[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle { - display: none !important; -} - -/* Uncomment this if you set helper : "clone" in draggable options */ -/*.gridster .player { - opacity:0; -} -*/ \ No newline at end of file diff --git a/lib/gridster/src/jquery.gridster.extras.js b/lib/gridster/src/jquery.gridster.extras.js index e7ea59f8b..c99bbd01c 100644 --- a/lib/gridster/src/jquery.gridster.extras.js +++ b/lib/gridster/src/jquery.gridster.extras.js @@ -1,13 +1,16 @@ ;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { + 'use strict'; + if(typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.gridster.js')); + } + else if (typeof define === 'function' && define.amd) { define(['jquery', 'gridster'], factory); } else { root.Gridster = factory(root.$ || root.jQuery, root.Gridster); } }(this, function($, Gridster) { - + 'use strict'; var fn = Gridster.prototype; fn.widgets_in_col = function(col) { @@ -34,32 +37,6 @@ return false; }; - - fn.widgets_in_range = function(col1, row1, col2, row2) { - var valid_cols = []; - var valid_rows = []; - var $widgets = $([]); - var c, r, $w, wgd; - - for (c = col2; c >= col1; c--) { - for (r = row2; r >= row1; r--) { - $w = this.is_widget(c, r); - - if ($w !== false) { - wgd = $w.data('coords').grid; - if (wgd.col >= col1 && wgd.col <= col2 && - wgd.row >= row1 && wgd.row <= row2 - ) { - $widgets = $widgets.add($w); - } - } - } - } - - return $widgets; - }; - - fn.get_bottom_most_occupied_cell = function() { var row = 0; var col = 0; @@ -108,7 +85,7 @@ }; - fn.next_position_in_range = function(size_x, size_y, max_rows) { + fn.next_position_in_range = function(size_x, size_y) { size_x || (size_x = 1); size_y || (size_y = 1); var ga = this.gridmap; @@ -117,12 +94,12 @@ var rows_l; for (var c = 1; c < cols_l; c++) { - rows_l = max_rows || ga[c].length; + rows_l = this.options.max_rows || ga[c].length; for (var r = 1; r <= rows_l; r++) { var can_move_to = this.can_move_to({ size_x: size_x, size_y: size_y - }, c, r, max_rows); + }, c, r); if (can_move_to) { valid_pos.push({ @@ -158,7 +135,6 @@ fn.closest_to_left = function(col, row) { - var cols_l = this.gridmap.length - 1; if (!this.gridmap[col]) { return false; } for (var c = col; c >= 1; c--) { diff --git a/lib/gridster/src/jquery.gridster.js b/lib/gridster/src/jquery.gridster.js index 7bac91186..7d4d041d4 100755 --- a/lib/gridster/src/jquery.gridster.js +++ b/lib/gridster/src/jquery.gridster.js @@ -6,3135 +6,4194 @@ * Licensed under the MIT licenses. */ -;(function(root, factory) { - - if (typeof define === 'function' && define.amd) { - define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); - } else { - root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, - root.GridsterCollision); - } - - }(this, function($, Draggable, Collision) { - - var defaults = { - namespace: '', - widget_selector: 'li', - widget_margins: [10, 10], - widget_base_dimensions: [400, 225], - extra_rows: 0, - extra_cols: 0, - min_cols: 1, - max_cols: Infinity, - min_rows: 15, - max_size_x: false, - autogrow_cols: false, - autogenerate_stylesheet: true, - avoid_overlapped_widgets: true, - auto_init: true, - serialize_params: function($w, wgd) { - return { - col: wgd.col, - row: wgd.row, - size_x: wgd.size_x, - size_y: wgd.size_y - }; - }, - collision: {}, - draggable: { - items: '.gs-w', - distance: 4, - ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) - }, - resize: { - enabled: false, - axes: ['both'], - handle_append_to: '', - handle_class: 'gs-resize-handle', - max_size: [Infinity, Infinity], - min_size: [1, 1] - } - }; - - /** - * @class Gridster - * @uses Draggable - * @uses Collision - * @param {HTMLElement} el The HTMLelement that contains all the widgets. - * @param {Object} [options] An Object with all options you want to - * overwrite: - * @param {HTMLElement|String} [options.widget_selector] Define who will - * be the draggable widgets. Can be a CSS Selector String or a - * collection of HTMLElements - * @param {Array} [options.widget_margins] Margin between widgets. - * The first index for the horizontal margin (left, right) and - * the second for the vertical margin (top, bottom). - * @param {Array} [options.widget_base_dimensions] Base widget dimensions - * in pixels. The first index for the width and the second for the - * height. - * @param {Number} [options.extra_cols] Add more columns in addition to - * those that have been calculated. - * @param {Number} [options.extra_rows] Add more rows in addition to - * those that have been calculated. - * @param {Number} [options.min_cols] The minimum required columns. - * @param {Number} [options.max_cols] The maximum columns possible (set to null - * for no maximum). - * @param {Number} [options.min_rows] The minimum required rows. - * @param {Number} [options.max_size_x] The maximum number of columns - * that a widget can span. - * @param {Boolean} [options.autogenerate_stylesheet] If true, all the - * CSS required to position all widgets in their respective columns - * and rows will be generated automatically and injected to the - * `` of the document. You can set this to false, and write - * your own CSS targeting rows and cols via data-attributes like so: - * `[data-col="1"] { left: 10px; }` - * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded - * from the DOM can be overlapped. It is helpful if the positions were - * bad stored in the database or if there was any conflict. - * @param {Boolean} [options.auto_init] Automatically call gridster init - * method or not when the plugin is instantiated. - * @param {Function} [options.serialize_params] Return the data you want - * for each widget in the serialization. Two arguments are passed: - * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid - * coords object (`col`, `row`, `size_x`, `size_y`). - * @param {Object} [options.collision] An Object with all options for - * Collision class you want to overwrite. See Collision docs for - * more info. - * @param {Object} [options.draggable] An Object with all options for - * Draggable class you want to overwrite. See Draggable docs for more - * info. - * @param {Object|Function} [options.draggable.ignore_dragging] Note that - * if you use a Function, and resize is enabled, you should ignore the - * resize handlers manually (options.resize.handle_class). - * @param {Object} [options.resize] An Object with resize config options. - * @param {Boolean} [options.resize.enabled] Set to true to enable - * resizing. - * @param {Array} [options.resize.axes] Axes in which widgets can be - * resized. Possible values: ['x', 'y', 'both']. - * @param {String} [options.resize.handle_append_to] Set a valid CSS - * selector to append resize handles to. - * @param {String} [options.resize.handle_class] CSS class name used - * by resize handles. - * @param {Array} [options.resize.max_size] Limit widget dimensions - * when resizing. Array values should be integers: - * `[max_cols_occupied, max_rows_occupied]` - * @param {Array} [options.resize.min_size] Limit widget dimensions - * when resizing. Array values should be integers: - * `[min_cols_occupied, min_rows_occupied]` - * @param {Function} [options.resize.start] Function executed - * when resizing starts. - * @param {Function} [otions.resize.resize] Function executed - * during the resizing. - * @param {Function} [options.resize.stop] Function executed - * when resizing stops. - * - * @constructor - */ - function Gridster(el, options) { - this.options = $.extend(true, {}, defaults, options); - this.$el = $(el); - this.$wrapper = this.$el.parent(); - this.$widgets = this.$el.children( - this.options.widget_selector).addClass('gs-w'); - this.widgets = []; - this.$changed = $([]); - this.wrapper_width = this.$wrapper.width(); - this.min_widget_width = (this.options.widget_margins[0] * 2) + - this.options.widget_base_dimensions[0]; - this.min_widget_height = (this.options.widget_margins[1] * 2) + - this.options.widget_base_dimensions[1]; - - this.generated_stylesheets = []; - this.$style_tags = $([]); - - this.options.auto_init && this.init(); - } - - Gridster.defaults = defaults; - Gridster.generated_stylesheets = []; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in ascending way. - * - * @method sort_by_row_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (!a.row) { - a = $(a).coords().grid; - b = $(b).coords().grid; - } - - if (a.row > b.row) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) placing first the empty cells upper left. - * - * @method sort_by_row_and_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_and_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row > b.row || a.row === b.row && a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects by column (representing the grid - * coords of each widget) in ascending way. - * - * @method sort_by_col_asc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_col_asc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.col > b.col) { - return 1; - } - return -1; - }); - - return widgets; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method sort_by_row_desc - * @param {Array} widgets Array of grid coords objects - * @return {Array} Returns the array sorted. - */ - Gridster.sort_by_row_desc = function(widgets) { - widgets = widgets.sort(function(a, b) { - if (a.row + a.size_y < b.row + b.size_y) { - return 1; - } - return -1; - }); - return widgets; - }; - - - - /** Instance Methods **/ - - var fn = Gridster.prototype; - - fn.init = function() { - this.options.resize.enabled && this.setup_resize(); - this.generate_grid_and_stylesheet(); - this.get_widgets_from_DOM(); - this.set_dom_grid_height(); - this.set_dom_grid_width(); - this.$wrapper.addClass('ready'); - this.draggable(); - this.options.resize.enabled && this.resizable(); - - $(window).bind('resize.gridster', throttle( - $.proxy(this.recalculate_faux_grid, this), 200)); - }; - - - /** - * Disables dragging. - * - * @method disable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.disable = function() { - this.$wrapper.find('.player-revert').removeClass('player-revert'); - this.drag_api.disable(); - return this; - }; - - - /** - * Enables dragging. - * - * @method enable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.enable = function() { - this.drag_api.enable(); - return this; - }; - - - - /** - * Disables drag-and-drop widget resizing. - * - * @method disable - * @return {Class} Returns instance of gridster Class. - */ - fn.disable_resize = function() { - this.$el.addClass('gs-resize-disabled'); - this.resize_api.disable(); - return this; - }; - - - /** - * Enables drag-and-drop widget resizing. - * - * @method enable - * @return {Class} Returns instance of gridster Class. - */ - fn.enable_resize = function() { - this.$el.removeClass('gs-resize-disabled'); - this.resize_api.enable(); - return this; - }; - - - /** - * Add a new widget to the grid. - * - * @method add_widget - * @param {String|HTMLElement} html The string representing the HTML of the widget - * or the HTMLElement. - * @param {Number} [size_x] The nº of rows the widget occupies horizontally. - * @param {Number} [size_y] The nº of columns the widget occupies vertically. - * @param {Number} [col] The column the widget should start in. - * @param {Number} [row] The row the widget should start in. - * @param {Array} [max_size] max_size Maximun size (in units) for width and height. - * @param {Array} [min_size] min_size Minimum size (in units) for width and height. - * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. - * the widget that was just created. - */ - fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) { - var pos; - size_x || (size_x = 1); - size_y || (size_y = 1); - - if (!col & !row) { - pos = this.next_position(size_x, size_y); - } else { - pos = { - col: col, - row: row, - size_x: size_x, - size_y: size_y - }; - - this.empty_cells(col, row, size_x, size_y); - } - - var $w = $(html).attr({ - 'data-col': pos.col, - 'data-row': pos.row, - 'data-sizex' : size_x, - 'data-sizey' : size_y - }).addClass('gs-w').appendTo(this.$el).hide(); - - this.$widgets = this.$widgets.add($w); - - this.register_widget($w); - - this.add_faux_rows(pos.size_y); - //this.add_faux_cols(pos.size_x); - - if (max_size) { - this.set_widget_max_size($w, max_size); - } - - if (min_size) { - this.set_widget_min_size($w, min_size); - } - - this.set_dom_grid_width(); - this.set_dom_grid_height(); - - this.drag_api.set_limits(this.cols * this.min_widget_width); - - return $w.fadeIn(); - }; - - - /** - * Change widget size limits. - * - * @method set_widget_min_size - * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement - * representing the widget or an index representing the desired widget. - * @param {Array} min_size Minimum size (in units) for width and height. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.set_widget_min_size = function($widget, min_size) { - $widget = typeof $widget === 'number' ? - this.$widgets.eq($widget) : $widget; - - if (!$widget.length) { return this; } - - var wgd = $widget.data('coords').grid; - wgd.min_size_x = min_size[0]; - wgd.min_size_y = min_size[1]; - - return this; - }; - - - /** - * Change widget size limits. - * - * @method set_widget_max_size - * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement - * representing the widget or an index representing the desired widget. - * @param {Array} max_size Maximun size (in units) for width and height. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.set_widget_max_size = function($widget, max_size) { - $widget = typeof $widget === 'number' ? - this.$widgets.eq($widget) : $widget; - - if (!$widget.length) { return this; } - - var wgd = $widget.data('coords').grid; - wgd.max_size_x = max_size[0]; - wgd.max_size_y = max_size[1]; - - return this; - }; - - - /** - * Append the resize handle into a widget. - * - * @method add_resize_handle - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.add_resize_handle = function($w) { - var append_to = this.options.resize.handle_append_to; - $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w); - - return this; - }; - - - /** - * Change the size of a widget. Width is limited to the current grid width. - * - * @method resize_widget - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget. - * @param {Number} size_x The number of columns that will occupy the widget. - * By default size_x is limited to the space available from - * the column where the widget begins, until the last column to the right. - * @param {Number} size_y The number of rows that will occupy the widget. - * @param {Function} [callback] Function executed when the widget is removed. - * @return {HTMLElement} Returns $widget. - */ - fn.resize_widget = function($widget, size_x, size_y, callback) { - var wgd = $widget.coords().grid; - var col = wgd.col; - var max_cols = this.options.max_cols; - var old_size_y = wgd.size_y; - var old_col = wgd.col; - var new_col = old_col; - - size_x || (size_x = wgd.size_x); - size_y || (size_y = wgd.size_y); - - if (max_cols !== Infinity) { - size_x = Math.min(size_x, max_cols - col + 1); - } - - if (size_y > old_size_y) { - this.add_faux_rows(Math.max(size_y - old_size_y, 0)); - } - - var player_rcol = (col + size_x - 1); - if (player_rcol > this.cols) { - this.add_faux_cols(player_rcol - this.cols); - } - - var new_grid_data = { - col: new_col, - row: wgd.row, - size_x: size_x, - size_y: size_y - }; - - this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); - - this.set_dom_grid_height(); - this.set_dom_grid_width(); - - if (callback) { - callback.call(this, new_grid_data.size_x, new_grid_data.size_y); - } - - return $widget; - }; - - - /** - * Mutate widget dimensions and position in the grid map. - * - * @method mutate_widget_in_gridmap - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement - * representing the widget to mutate. - * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). - * @param {Object} new_wgd New widget grid data. - * @return {HTMLElement} Returns instance of gridster Class. - */ - fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) { - var old_size_x = wgd.size_x; - var old_size_y = wgd.size_y; - - var old_cells_occupied = this.get_cells_occupied(wgd); - var new_cells_occupied = this.get_cells_occupied(new_wgd); - - var empty_cols = []; - $.each(old_cells_occupied.cols, function(i, col) { - if ($.inArray(col, new_cells_occupied.cols) === -1) { - empty_cols.push(col); - } - }); - - var occupied_cols = []; - $.each(new_cells_occupied.cols, function(i, col) { - if ($.inArray(col, old_cells_occupied.cols) === -1) { - occupied_cols.push(col); - } - }); - - var empty_rows = []; - $.each(old_cells_occupied.rows, function(i, row) { - if ($.inArray(row, new_cells_occupied.rows) === -1) { - empty_rows.push(row); - } - }); - - var occupied_rows = []; - $.each(new_cells_occupied.rows, function(i, row) { - if ($.inArray(row, old_cells_occupied.rows) === -1) { - occupied_rows.push(row); - } - }); - - this.remove_from_gridmap(wgd); - - if (occupied_cols.length) { - var cols_to_empty = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget - ]; - this.empty_cells.apply(this, cols_to_empty); - } - - if (occupied_rows.length) { - var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; - this.empty_cells.apply(this, rows_to_empty); - } - - // not the same that wgd = new_wgd; - wgd.col = new_wgd.col; - wgd.row = new_wgd.row; - wgd.size_x = new_wgd.size_x; - wgd.size_y = new_wgd.size_y; - - this.add_to_gridmap(new_wgd, $widget); - - $widget.removeClass('player-revert'); - - //update coords instance attributes - $widget.data('coords').update({ - width: (new_wgd.size_x * this.options.widget_base_dimensions[0] + - ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2), - height: (new_wgd.size_y * this.options.widget_base_dimensions[1] + - ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2) - }); - - $widget.attr({ - 'data-col': new_wgd.col, - 'data-row': new_wgd.row, - 'data-sizex': new_wgd.size_x, - 'data-sizey': new_wgd.size_y - }); - - if (empty_cols.length) { - var cols_to_remove_holes = [ - empty_cols[0], new_wgd.row, - empty_cols.length, - Math.min(old_size_y, new_wgd.size_y), - $widget - ]; - - this.remove_empty_cells.apply(this, cols_to_remove_holes); - } - - if (empty_rows.length) { - var rows_to_remove_holes = [ - new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget - ]; - this.remove_empty_cells.apply(this, rows_to_remove_holes); - } - - this.move_widget_up($widget); - - return this; - }; - - - /** - * Move down widgets in cells represented by the arguments col, row, size_x, - * size_y - * - * @method empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} $exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells = function(col, row, size_x, size_y, $exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row - size_y, - size_x: size_x, - size_y: size_y - }); - - $nexts.not($exclude).each($.proxy(function(i, w) { - var wgd = $(w).coords().grid; - if ( !(wgd.row <= (row + size_y - 1))) { return; } - var diff = (row + size_y) - wgd.row; - this.move_widget_down($(w), diff); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Move up widgets below cells represented by the arguments col, row, size_x, - * size_y. - * - * @method remove_empty_cells - * @param {Number} col The column where the group of cells begin. - * @param {Number} row The row where the group of cells begin. - * @param {Number} size_x The number of columns that the group of cells - * occupy. - * @param {Number} size_y The number of rows that the group of cells - * occupy. - * @param {HTMLElement} exclude Exclude widgets from being moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { - var $nexts = this.widgets_below({ - col: col, - row: row, - size_x: size_x, - size_y: size_y - }); - - $nexts.not(exclude).each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), size_y ); - }, this)); - - this.set_dom_grid_height(); - - return this; - }; - - - /** - * Get the most left column below to add a new widget. - * - * @method next_position - * @param {Number} size_x The nº of rows the widget occupies horizontally. - * @param {Number} size_y The nº of columns the widget occupies vertically. - * @return {Object} Returns a grid coords object representing the future - * widget coords. - */ - fn.next_position = function(size_x, size_y) { - size_x || (size_x = 1); - size_y || (size_y = 1); - var ga = this.gridmap; - var cols_l = ga.length; - var valid_pos = []; - var rows_l; - - for (var c = 1; c < cols_l; c++) { - rows_l = ga[c].length; - for (var r = 1; r <= rows_l; r++) { - var can_move_to = this.can_move_to({ - size_x: size_x, - size_y: size_y - }, c, r); - - if (can_move_to) { - valid_pos.push({ - col: c, - row: r, - size_y: size_y, - size_x: size_x - }); - } - } - } - - if (valid_pos.length) { - return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; - } - return false; - }; - - - /** - * Remove a widget from the grid. - * - * @method remove_widget - * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. - * @param {Boolean|Function} silent If true, widgets below the removed one - * will not move up. If a Function is passed it will be used as callback. - * @param {Function} callback Function executed when the widget is removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_widget = function(el, silent, callback) { - var $el = el instanceof $ ? el : $(el); - var wgd = $el.coords().grid; - - // if silent is a function assume it's a callback - if ($.isFunction(silent)) { - callback = silent; - silent = false; - } - - this.cells_occupied_by_placeholder = {}; - this.$widgets = this.$widgets.not($el); - - var $nexts = this.widgets_below($el); - - this.remove_from_gridmap(wgd); - - $el.fadeOut($.proxy(function() { - $el.remove(); - - if (!silent) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget), wgd.size_y ); - }, this)); - } - - this.set_dom_grid_height(); - - if (callback) { - callback.call(this, el); - } - }, this)); - - return this; - }; - - - /** - * Remove all widgets from the grid. - * - * @method remove_all_widgets - * @param {Function} callback Function executed for each widget removed. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_all_widgets = function(callback) { - this.$widgets.each($.proxy(function(i, el){ - this.remove_widget(el, true, callback); - }, this)); - - return this; - }; - - - /** - * Returns a serialized array of the widgets in the grid. - * - * @method serialize - * @param {HTMLElement} [$widgets] The collection of jQuery wrapped - * HTMLElements you want to serialize. If no argument is passed all widgets - * will be serialized. - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize = function($widgets) { - $widgets || ($widgets = this.$widgets); - - return $widgets.map($.proxy(function(i, widget) { - var $w = $(widget); - return this.options.serialize_params($w, $w.coords().grid); - }, this)).get(); - }; - - - /** - * Returns a serialized array of the widgets that have changed their - * position. - * - * @method serialize_changed - * @return {Array} Returns an Array of Objects with the data specified in - * the serialize_params option. - */ - fn.serialize_changed = function() { - return this.serialize(this.$changed); - }; - - - /** - * Convert widgets from DOM elements to "widget grid data" Objects. - * - * @method dom_to_coords - * @param {HTMLElement} $widget The widget to be converted. - */ - fn.dom_to_coords = function($widget) { - return { - 'col': parseInt($widget.attr('data-col'), 10), - 'row': parseInt($widget.attr('data-row'), 10), - 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, - 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, - 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, - 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, - 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, - 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, - 'el': $widget - }; - }; - - - /** - * Creates the grid coords object representing the widget an add it to the - * mapped array of positions. - * - * @method register_widget - * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing - * the widget, or an "widget grid data" Object with (col, row, el ...). - * @return {Boolean} Returns true if the widget final position is different - * than the original. - */ - fn.register_widget = function($el) { - var isDOM = $el instanceof jQuery; - var wgd = isDOM ? this.dom_to_coords($el) : $el; - var posChanged = false; - isDOM || ($el = wgd.el); - - var empty_upper_row = this.can_go_widget_up(wgd); - if (empty_upper_row) { - wgd.row = empty_upper_row; - $el.attr('data-row', empty_upper_row); - this.$el.trigger('gridster:positionchanged', [wgd]); - posChanged = true; - } - - if (this.options.avoid_overlapped_widgets && - !this.can_move_to( - {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) - ) { - $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); - $el.attr({ - 'data-col': wgd.col, - 'data-row': wgd.row, - 'data-sizex': wgd.size_x, - 'data-sizey': wgd.size_y - }); - posChanged = true; - } - - // attach Coord object to player data-coord attribute - $el.data('coords', $el.coords()); - // Extend Coord object with grid position info - $el.data('coords').grid = wgd; - - this.add_to_gridmap(wgd, $el); - - this.options.resize.enabled && this.add_resize_handle($el); - - return posChanged; - }; - - - /** - * Update in the mapped array of positions the value of cells represented by - * the grid coords object passed in the `grid_data` param. - * - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped - * HTMLElement, depends if you want to delete an existing position or add - * a new one. - * @method update_widget_position - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.update_widget_position = function(grid_data, value) { - this.for_each_cell_occupied(grid_data, function(col, row) { - if (!this.gridmap[col]) { return this; } - this.gridmap[col][row] = value; - }); - return this; - }; - - - /** - * Remove a widget from the mapped array of positions. - * - * @method remove_from_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.remove_from_gridmap = function(grid_data) { - return this.update_widget_position(grid_data, false); - }; - - - /** - * Add a widget to the mapped array of positions. - * - * @method add_to_gridmap - * @param {Object} grid_data The grid coords object representing the cells - * to update in the mapped array. - * @param {HTMLElement|Boolean} value The value to set in the specified - * position . - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.add_to_gridmap = function(grid_data, value) { - this.update_widget_position(grid_data, value || grid_data.el); - - if (grid_data.el) { - var $widgets = this.widgets_below(grid_data.el); - $widgets.each($.proxy(function(i, widget) { - this.move_widget_up( $(widget)); - }, this)); - } - }; - - - /** - * Make widgets draggable. - * - * @uses Draggable - * @method draggable - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.draggable = function() { - var self = this; - var draggable_options = $.extend(true, {}, this.options.draggable, { - offset_left: this.options.widget_margins[0], - offset_top: this.options.widget_margins[1], - container_width: this.cols * this.min_widget_width, - limit: true, - start: function(event, ui) { - self.$widgets.filter('.player-revert') - .removeClass('player-revert'); - - self.$player = $(this); - self.$helper = $(ui.$helper); - - self.helper = !self.$helper.is(self.$player); - - self.on_start_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstart'); - }, - stop: function(event, ui) { - self.on_stop_drag.call(self, event, ui); - self.$el.trigger('gridster:dragstop'); - }, - drag: throttle(function(event, ui) { - self.on_drag.call(self, event, ui); - self.$el.trigger('gridster:drag'); - }, 60) - }); - - this.drag_api = this.$el.drag(draggable_options); - return this; - }; - - - /** - * Bind resize events to get resize working. - * - * @method resizable - * @return {Class} Returns instance of gridster Class. - */ - fn.resizable = function() { - this.resize_api = this.$el.drag({ - items: '.' + this.options.resize.handle_class, - offset_left: this.options.widget_margins[0], - container_width: this.container_width, - move_element: false, - resize: true, - limit: this.options.autogrow_cols ? false : true, - start: $.proxy(this.on_start_resize, this), - stop: $.proxy(function(event, ui) { - delay($.proxy(function() { - this.on_stop_resize(event, ui); - }, this), 120); - }, this), - drag: throttle($.proxy(this.on_resize, this), 60) - }); - - return this; - }; - - - /** - * Setup things required for resizing. Like build templates for drag handles. - * - * @method setup_resize - * @return {Class} Returns instance of gridster Class. - */ - fn.setup_resize = function() { - this.resize_handle_class = this.options.resize.handle_class; - var axes = this.options.resize.axes; - var handle_tpl = ''; - - this.resize_handle_tpl = $.map(axes, function(type) { - return handle_tpl.replace('{type}', type); - }).join(''); - - if ($.isArray(this.options.draggable.ignore_dragging)) { - this.options.draggable.ignore_dragging.push( - '.' + this.resize_handle_class); - } - - return this; - }; - - - /** - * This function is executed when the player begins to be dragged. - * - * @method on_start_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_start_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); - - this.highest_col = this.get_highest_occupied_cell().col; - - this.$player.addClass('player'); - this.player_grid_data = this.$player.coords().grid; - this.placeholder_grid_data = $.extend({}, this.player_grid_data); - - this.set_dom_grid_height(this.$el.height() + - (this.player_grid_data.size_y * this.min_widget_height)); - - this.set_dom_grid_width(this.cols); - - var pgd_sizex = this.player_grid_data.size_x; - var cols_diff = this.cols - this.highest_col; - - if (this.options.autogrow_cols && cols_diff <= pgd_sizex) { - this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); - } - - var colliders = this.faux_grid; - var coords = this.$player.data('coords').coords; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.last_cols = []; - this.last_rows = []; - - // see jquery.collision.js - this.collision_api = this.$helper.collision( - colliders, this.options.collision); - - this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { - 'class': 'preview-holder', - 'data-row': this.$player.attr('data-row'), - 'data-col': this.$player.attr('data-col'), - css: { - width: coords.width, - height: coords.height - } - }).appendTo(this.$el); - - if (this.options.draggable.start) { - this.options.draggable.start.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player is being dragged. - * - * @method on_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_drag = function(event, ui) { - //break if dragstop has been fired - if (this.$player === null) { - return false; - } - - var abs_offset = { - left: ui.position.left + this.baseX, - top: ui.position.top + this.baseY - }; - - // auto grow cols - if (this.options.autogrow_cols) { - var prcol = this.placeholder_grid_data.col + - this.placeholder_grid_data.size_x - 1; - - // "- 1" due to adding at least 1 column in on_start_drag - if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { - this.add_faux_cols(1); - this.set_dom_grid_width(this.cols + 1); - this.drag_api.set_limits(this.container_width); - } - - this.collision_api.set_colliders(this.faux_grid); - } - - this.colliders_data = this.collision_api.get_closest_colliders( - abs_offset); - - this.on_overlapped_column_change( - this.on_start_overlapping_column, this.on_stop_overlapping_column); - - this.on_overlapped_row_change( - this.on_start_overlapping_row, this.on_stop_overlapping_row); - - - if (this.helper && this.$player) { - this.$player.css({ - 'left': ui.position.left, - 'top': ui.position.top - }); - } - - if (this.options.draggable.drag) { - this.options.draggable.drag.call(this, event, ui); - } - }; - - - /** - * This function is executed when the player stops being dragged. - * - * @method on_stop_drag - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_stop_drag = function(event, ui) { - this.$helper.add(this.$player).add(this.$wrapper) - .removeClass('dragging'); - - ui.position.left = ui.position.left + this.baseX; - ui.position.top = ui.position.top + this.baseY; - this.colliders_data = this.collision_api.get_closest_colliders( - ui.position); - - this.on_overlapped_column_change( - this.on_start_overlapping_column, - this.on_stop_overlapping_column - ); - - this.on_overlapped_row_change( - this.on_start_overlapping_row, - this.on_stop_overlapping_row - ); - - this.$player.addClass('player-revert').removeClass('player') - .attr({ - 'data-col': this.placeholder_grid_data.col, - 'data-row': this.placeholder_grid_data.row - }).css({ - 'left': '', - 'top': '' - }); - - this.$changed = this.$changed.add(this.$player); - - this.cells_occupied_by_player = this.get_cells_occupied( - this.placeholder_grid_data); - this.set_cells_player_occupies( - this.placeholder_grid_data.col, this.placeholder_grid_data.row); - - this.$player.coords().grid.row = this.placeholder_grid_data.row; - this.$player.coords().grid.col = this.placeholder_grid_data.col; - - if (this.options.draggable.stop) { - this.options.draggable.stop.call(this, event, ui); - } - - this.$preview_holder.remove(); - - this.$player = null; - this.$helper = null; - this.placeholder_grid_data = {}; - this.player_grid_data = {}; - this.cells_occupied_by_placeholder = {}; - this.cells_occupied_by_player = {}; - - this.set_dom_grid_height(); - this.set_dom_grid_width(); - - if (this.options.autogrow_cols) { - this.drag_api.set_limits(this.cols * this.min_widget_width); - } - }; - - - /** - * This function is executed every time a widget starts to be resized. - * - * @method on_start_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_start_resize = function(event, ui) { - this.$resized_widget = ui.$player.closest('.gs-w'); - this.resize_coords = this.$resized_widget.coords(); - this.resize_wgd = this.resize_coords.grid; - this.resize_initial_width = this.resize_coords.coords.width; - this.resize_initial_height = this.resize_coords.coords.height; - this.resize_initial_sizex = this.resize_coords.grid.size_x; - this.resize_initial_sizey = this.resize_coords.grid.size_y; - this.resize_initial_col = this.resize_coords.grid.col; - this.resize_last_sizex = this.resize_initial_sizex; - this.resize_last_sizey = this.resize_initial_sizey; - - this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || - this.options.resize.max_size[0], - this.options.max_cols - this.resize_initial_col + 1); - this.resize_max_size_y = this.resize_wgd.max_size_y || - this.options.resize.max_size[1]; - - this.resize_min_size_x = (this.resize_wgd.min_size_x || - this.options.resize.min_size[0] || 1); - this.resize_min_size_y = (this.resize_wgd.min_size_y || - this.options.resize.min_size[1] || 1); - - this.resize_initial_last_col = this.get_highest_occupied_cell().col; - - this.set_dom_grid_width(this.cols); - - this.resize_dir = { - right: ui.$player.is('.' + this.resize_handle_class + '-x'), - bottom: ui.$player.is('.' + this.resize_handle_class + '-y') - }; - - this.$resized_widget.css({ - 'min-width': this.options.widget_base_dimensions[0], - 'min-height': this.options.widget_base_dimensions[1] - }); - - var nodeName = this.$resized_widget.get(0).tagName; - this.$resize_preview_holder = $('<' + nodeName + ' />', { - 'class': 'preview-holder resize-preview-holder', - 'data-row': this.$resized_widget.attr('data-row'), - 'data-col': this.$resized_widget.attr('data-col'), - 'css': { - 'width': this.resize_initial_width, - 'height': this.resize_initial_height - } - }).appendTo(this.$el); - - this.$resized_widget.addClass('resizing'); +(function (root, factory) { + 'use strict'; + if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('./jquery.draggable.js'), require('./jquery.collision.js'), require('./jquery.coords.js'), require('./utils.js')); + } + else if (typeof define === 'function' && define.amd) { + define(['jquery', 'gridster-draggable', 'gridster-collision'], factory); + } else { + root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable, + root.GridsterCollision); + } + +}(this, function ($, Draggable, Collision) { + 'use strict'; + var $window = $(window), + defaults = { + namespace: '', + widget_selector: 'li', + static_class: 'static', + widget_margins: [10, 10], + widget_base_dimensions: [400, 225], + extra_rows: 0, + extra_cols: 0, + min_cols: 1, + max_cols: Infinity, + min_rows: 1, + max_rows: 15, + autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, + auto_init: true, + center_widgets: false, + responsive_breakpoint: false, + scroll_container: window, + shift_larger_widgets_down: true, + shift_widgets_up: true, + show_element: function($el, callback) { + if (callback) { + $el.fadeIn(callback); + } else { + $el.fadeIn(); + } + }, + hide_element: function($el, callback) { + if (callback) { + $el.fadeOut(callback); + } else { + $el.fadeOut(); + } + }, + serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + }, + collision: { + wait_for_mouseup: false + }, + draggable: { + items: '.gs-w:not(.static)', + distance: 4, + ignore_dragging: Draggable.defaults.ignore_dragging.slice(0) + }, + resize: { + enabled: false, + axes: ['both'], + handle_append_to: '', + handle_class: 'gs-resize-handle', + max_size: [Infinity, Infinity], + min_size: [1, 1] + } + }; + + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.max_cols] The maximum columns possible (set to null + * for no maximum). + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Boolean} [options.auto_init] Automatically call gridster init + * method or not when the plugin is instantiated. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Boolean} [options.shift_larger_widgets_down] Determines if how widgets get pushes + * out of the way of the player. If set to false smaller widgets will not move larger + * widgets out of their way . Defaults to true. + * @param {Boolean} [options.shift_widgets_up] Determines if the player will automatically + * condense the grid and not allow a widget to have space above it. Defaults to true. + * @param {Function} [options.show_element] Makes the given element visible. Two arguments are passed: + * `$el`: the jQuery wrapped HTMLElement, and `callback`: a function that is executed + * after the element is made visible. The callback parameter is optional. + * @param {Function} [options.hide_element] Hides the given element. Two arguments are passed: + * `$el`: the jQuery wrapped HTMLElement, and `callback`: a function that is executed + * after the element is hidden. The callback parameter is optional. + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Boolean} [options.collision.wait_for_mouseup] Default is false. + * If true then it will not move colliding widgets during drag, but only on + * mouseup. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more info. + * @param {Object|Function} [options.draggable.ignore_dragging] Note that + * if you use a Function, and resize is enabled, you should ignore the + * resize handlers manually (options.resize.handle_class). + * @param {Object} [options.resize] An Object with resize config options. + * @param {Boolean} [options.resize.enabled] Set to true to enable + * resizing. + * @param {Array} [options.resize.axes] Axes in which widgets can be + * resized. Possible values: ['x', 'y', 'both']. + * @param {String} [options.resize.handle_append_to] Set a valid CSS + * selector to append resize handles to. + * @param {String} [options.resize.handle_class] CSS class name used + * by resize handles. + * @param {Array} [options.resize.max_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[max_cols_occupied, max_rows_occupied]` + * @param {Array} [options.resize.min_size] Limit widget dimensions + * when resizing. Array values should be integers: + * `[min_cols_occupied, min_rows_occupied]` + * @param {Function} [options.resize.start] Function executed + * when resizing starts. + * @param {Function} [options.resize.resize] Function executed + * during the resizing. + * @param {Function} [options.resize.stop] Function executed + * when resizing stops. + * + * @constructor + */ + function Gridster (el, options) { + this.options = $.extend(true, {}, defaults, options); + this.options.draggable = this.options.draggable || {}; + this.options.draggable = $.extend(true, {}, this.options.draggable, + {scroll_container: this.options.scroll_container}); + this.$el = $(el); + this.$scroll_container = this.options.scroll_container === window ? + $(window) : this.$el.closest(this.options.scroll_container); + this.$wrapper = this.$el.parent(); + this.$widgets = this.$el.children(this.options.widget_selector).addClass('gs-w'); + this.$changed = $([]); + this.w_queue = {}; + if (this.is_responsive()) { + this.min_widget_width = this.get_responsive_col_width(); + } else { + this.min_widget_width = this.options.widget_base_dimensions[0]; + } + this.min_widget_height = this.options.widget_base_dimensions[1]; + + this.min_col_count = this.options.min_cols; + this.prev_col_count = this.min_col_count; + + this.generated_stylesheets = []; + this.$style_tags = $([]); + + this.options.auto_init && this.init(); + } + + Gridster.defaults = defaults; + Gridster.generated_stylesheets = []; + + /** + * Convert properties to Integer + * + * @param {Object} obj - config object to be converted + * @return {Object} Returns the converted object. + */ + function convInt (obj) { + var props = ['col', 'row', 'size_x', 'size_y']; + var tmp = {}; + for (var i = 0, len = props.length; i < len; i++) { + var prop = props[i]; + if (!(prop in obj)) { + throw new Error('Not exists property `' + prop + '`'); + } + var val = obj[prop]; + if (!val || isNaN(val)) { + throw new Error('Invalid value of `' + prop + '` property'); + } + tmp[prop] = +val; + } + return tmp; + } + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + } + + a = convInt(a); + b = convInt(b); + if (a.row > b.row) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_and_col_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.row > b.row || a.row === b.row && a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_col_asc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + Gridster.sort_by_row_desc = function (widgets) { + widgets = widgets.sort(function (a, b) { + a = convInt(a); + b = convInt(b); + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + + /** Instance Methods **/ + + var fn = Gridster.prototype; + + fn.init = function () { + this.options.resize.enabled && this.setup_resize(); + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + this.$wrapper.addClass('ready'); + this.draggable(); + this.options.resize.enabled && this.resizable(); + + if (this.options.center_widgets) { + setTimeout($.proxy(function () { + this.center_widgets(); + }, this), 0); + } + + $window.bind('resize.gridster', throttle( + $.proxy(this.recalculate_faux_grid, this), 200)); + }; + + + /** + * Disables dragging. + * + * @method disable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.disable = function () { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + return this; + }; + + + /** + * Enables dragging. + * + * @method enable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.enable = function () { + this.drag_api.enable(); + return this; + }; + + + /** + * Disables drag-and-drop widget resizing. + * + * @method disable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.disable_resize = function () { + this.$el.addClass('gs-resize-disabled'); + this.resize_api.disable(); + return this; + }; + + + /** + * Enables drag-and-drop widget resizing. + * + * @method enable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.enable_resize = function () { + this.$el.removeClass('gs-resize-disabled'); + this.resize_api.enable(); + return this; + }; + + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @param {Array} [max_size] max_size Maximun size (in units) for width and height. + * @param {Array} [min_size] min_size Minimum size (in units) for width and height. + * @param {Function} [callback] Function executed after the widget is shown. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function (html, size_x, size_y, col, row, max_size, min_size, callback) { + var pos; + size_x || (size_x = 1); + size_y || (size_y = 1); + + if (!col && !row) { + pos = this.next_position(size_x, size_y); + } else { + pos = { + col: col, + row: row, + size_x: size_x, + size_y: size_y + }; + if (this.options.avoid_overlapped_widgets) { + this.empty_cells(col, row, size_x, size_y); + } + } + + var $w = $(html).attr({ + 'data-col': pos.col, + 'data-row': pos.row, + 'data-sizex': size_x, + 'data-sizey': size_y + }).addClass('gs-w').appendTo(this.$el).hide(); + + this.$widgets = this.$widgets.add($w); + this.$changed = this.$changed.add($w); + + this.register_widget($w); + + var reqRows = parseInt(pos.row) + (parseInt(pos.size_y) - 1); + if (this.rows < reqRows) { + this.add_faux_rows(reqRows - this.rows); + } + + if (max_size) { + this.set_widget_max_size($w, max_size); + } + + if (min_size) { + this.set_widget_min_size($w, min_size); + } + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + + if (this.options.center_widgets) { + setTimeout($.proxy(function () { + this.center_widgets(); + }, this), 0); + } + + this.options.show_element.call(this, $w, callback); + + return $w; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_min_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} min_size Minimum size (in grid units) for width and height. + * @return {Gridster} Returns instance of gridster Class. + */ + fn.set_widget_min_size = function ($widget, min_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { + return this; + } + + var wgd = $widget.data('coords').grid; + wgd.min_size_x = min_size[0]; + wgd.min_size_y = min_size[1]; + + return this; + }; + + + /** + * Change widget size limits. + * + * @method set_widget_max_size + * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement + * representing the widget or an index representing the desired widget. + * @param {Array} max_size Maximun size (in units) for width and height. + * @return {Gridster} Returns instance of gridster Class. + */ + fn.set_widget_max_size = function ($widget, max_size) { + $widget = typeof $widget === 'number' ? + this.$widgets.eq($widget) : $widget; + + if (!$widget.length) { + return this; + } + + var wgd = $widget.data('coords').grid; + wgd.max_size_x = max_size[0]; + wgd.max_size_y = max_size[1]; + + return this; + }; + + + /** + * Append the resize handle into a widget. + * + * @method add_resize_handle + * representing the widget. + * @return {HTMLElement} Returns instance of gridster Class. + * @param $w + */ + fn.add_resize_handle = function ($w) { + var $append_to = this.options.resize.handle_append_to ? $(this.options.resize.handle_append_to, $w) : $w; + + if (($append_to.children('span[class~=\'' + this.resize_handle_class + '\']')).length === 0) { + $(this.resize_handle_tpl).appendTo($append_to); + } + + return this; + }; + + + /** + * Change the size of a widget. Width is limited to the current grid width. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} [size_x] The number of columns that will occupy the widget. + * By default size_x is limited to the space available from + * the column where the widget begins, until the last column to the right. + * @param {Number} [size_y] The number of rows that will occupy the widget. + * @param {Function} [callback] Function executed when the widget is removed. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function ($widget, size_x, size_y, callback) { + var wgd = $widget.coords().grid; + + size_x || (size_x = wgd.size_x); + size_y || (size_y = wgd.size_y); + + //ensure the grid has the correct number of rows + if (!this.is_valid_row(wgd.row, size_y)){ + this.add_faux_rows(Math.max(this.calculate_highest_row(wgd.row, size_y) - this.rows, 0)); + } + + //ensure the grid has the correct number of cols + if (!this.is_valid_col(wgd.col, size_y)) { + this.add_faux_cols(Math.max(this.calculate_highest_row(wgd.col, size_x) - this.cols, 0)); + } + + var new_grid_data = { + col: wgd.col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Expand the widget. Width is set to the current grid width. + * + * @method expand_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of cols that will occupy the widget. + * @param {Number} size_y The number of rows that will occupy the widget. + * @param {Number} col The column to resize the widget from. + * @param {Function} [callback] Function executed when the widget is expanded. + * @return {HTMLElement} Returns $widget. + */ + fn.expand_widget = function ($widget, size_x, size_y, col, callback) { + var wgd = $widget.coords().grid; + var max_size_x = Math.floor(($(window).width() - this.options.widget_margins[0] * 2) / this.min_widget_width); + size_x = size_x || Math.min(max_size_x, this.cols); + size_y || (size_y = wgd.size_y); + + var old_size_y = wgd.size_y; + $widget.attr('pre_expand_col', wgd.col); + $widget.attr('pre_expand_sizex', wgd.size_x); + $widget.attr('pre_expand_sizey', wgd.size_y); + var new_col = col || 1; + + if (size_y > old_size_y) { + this.add_faux_rows(Math.max(size_y - old_size_y, 0)); + } + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Collapse the widget to it's pre-expanded size + * + * @method collapse_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Function} [callback] Function executed when the widget is collapsed. + * @return {HTMLElement} Returns $widget. + */ + fn.collapse_widget = function ($widget, callback) { + var wgd = $widget.coords().grid; + var size_x = parseInt($widget.attr('pre_expand_sizex')); + var size_y = parseInt($widget.attr('pre_expand_sizey')); + + var new_col = parseInt($widget.attr('pre_expand_col')); + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + /** + * Fit the size of a widget to its content (best guess) + * + * @method fit_to_content + * @param $widget {HTMLElement} $widget The jQuery wrapped HTMLElement + * @param max_cols {Number} max number of columns a widget can take up + * @param max_rows {Number} max number of rows a widget can take up + * @param {Function} [callback] Function executed when the widget is fit to content. + * @return {HTMLElement} Returns $widget. + */ + fn.fit_to_content = function ($widget, max_cols, max_rows, callback) { + var wgd = $widget.coords().grid; + var width = this.$wrapper.width(); + var height = this.$wrapper.height(); + var col_size = this.options.widget_base_dimensions[0] + (2 * this.options.widget_margins[0]); + var row_size = this.options.widget_base_dimensions[1] + (2 * this.options.widget_margins[1]); + var best_cols = Math.ceil((width + (2 * this.options.widget_margins[0])) / col_size); + var best_rows = Math.ceil((height + (2 * this.options.widget_margins[1])) / row_size); + + var new_grid_data = { + col: wgd.col, + row: wgd.row, + size_x: Math.min(max_cols, best_cols), + size_y: Math.min(max_rows, best_rows) + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.size_x, new_grid_data.size_y); + } + + return $widget; + }; + + + /** + * Centers widgets in grid + * + * @method center_widgets + */ + fn.center_widgets = debounce(function () { + var wrapper_width = this.$wrapper.width(); + var col_size; + if (this.is_responsive()) { + col_size = this.get_responsive_col_width(); + } else { + col_size = this.options.widget_base_dimensions[0] + (2 * this.options.widget_margins[0]); + } + var col_count = Math.floor(Math.max(Math.floor(wrapper_width / col_size), this.min_col_count) / 2) * 2; + + this.options.min_cols = col_count; + this.options.max_cols = col_count; + this.options.extra_cols = 0; + this.set_dom_grid_width(col_count); + this.cols = col_count; + + var col_dif = (col_count - this.prev_col_count) / 2; + + if (col_dif < 0) { + if (this.get_min_col() > col_dif * -1) { + this.shift_cols(col_dif); + } else { + this.resize_widget_dimensions(this.options); + } + + setTimeout($.proxy(function () { + this.resize_widget_dimensions(this.options); + }, this), 0); + + } else if (col_dif > 0) { + this.resize_widget_dimensions(this.options); + + setTimeout($.proxy(function () { + this.shift_cols(col_dif); + }, this), 0); + + } else { + this.resize_widget_dimensions(this.options); + + setTimeout($.proxy(function () { + this.resize_widget_dimensions(this.options); + }, this), 0); + + } + + this.prev_col_count = col_count; + return this; + }, 200); + + + fn.get_min_col = function () { + return Math.min.apply(Math, this.$widgets.map($.proxy(function (key, widget) { + return this.get_cells_occupied($(widget).coords().grid).cols; + }, this)).get()); + }; + + + fn.shift_cols = function (col_dif) { + var widgets_coords = this.$widgets.map($.proxy(function (i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + widgets_coords.each($.proxy(function (i, widget) { + var $widget = $(widget.el); + var wgd = $widget.coords().grid; + var col = parseInt($widget.attr('data-col')); + + var new_grid_data = { + col: Math.max(Math.round(col + col_dif), 1), + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + setTimeout($.proxy(function () { + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + }, this), 0); + }, this)); + }; + + + /** + * Change the dimensions of widgets. + * + * @method resize_widget_dimensions + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.resize_widget_dimensions = function (options) { + if (options.widget_margins) { + this.options.widget_margins = options.widget_margins; + } + + if (options.widget_base_dimensions) { + this.options.widget_base_dimensions = options.widget_base_dimensions; + } + + this.min_widget_width = (this.options.widget_margins[0] * 2) + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + this.options.widget_base_dimensions[1]; + + this.$widgets.each($.proxy(function (i, widget) { + var $widget = $(widget); + this.resize_widget($widget); + }, this)); + + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Mutate widget dimensions and position in the grid map. + * + * @method mutate_widget_in_gridmap + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget to mutate. + * @param {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @param {Object} new_wgd New widget grid data. + * @return {HTMLElement} Returns instance of gridster Class. + */ + fn.mutate_widget_in_gridmap = function ($widget, wgd, new_wgd) { + var old_size_y = wgd.size_y; + + var old_cells_occupied = this.get_cells_occupied(wgd); + var new_cells_occupied = this.get_cells_occupied(new_wgd); + + //find the cells that this widget currently occupies + var empty_cols = []; + $.each(old_cells_occupied.cols, function (i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + } + }); + + //find the cells that this widget will occupy + var occupied_cols = []; + $.each(new_cells_occupied.cols, function (i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + //find the rows that it currently occupies + var empty_rows = []; + $.each(old_cells_occupied.rows, function (i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + } + }); + + //find the rows that it will occupy + var occupied_rows = []; + $.each(new_cells_occupied.rows, function (i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + } + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget + ]; + this.empty_cells.apply(this, cols_to_empty); + } + + if (occupied_rows.length) { + var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + } + + // not the same that wgd = new_wgd; + wgd.col = new_wgd.col; + wgd.row = new_wgd.row; + wgd.size_x = new_wgd.size_x; + wgd.size_y = new_wgd.size_y; + + this.add_to_gridmap(new_wgd, $widget); + + $widget.removeClass('player-revert'); + + this.update_widget_dimensions($widget, new_wgd); + + if (empty_cols.length) { + var cols_to_remove_holes = [ + empty_cols[0], new_wgd.row, + empty_cols[empty_cols.length - 1] - empty_cols[0] + 1, + Math.min(old_size_y, new_wgd.size_y), + $widget + ]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [ + new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget + ]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + this.move_widget_up($widget); + + return this; + }; + + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} [$exclude] Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function (col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row - size_y, + size_x: size_x, + size_y: size_y + }); + + $nexts.not($exclude).each($.proxy(function (i, w) { + var $w = $(w), + wgd = $w.coords().grid; + /*jshint -W018 */ + if (!(wgd.row <= (row + size_y - 1))) { + return; + } + /*jshint +W018 */ + var diff = (row + size_y) - wgd.row; + this.move_widget_down($w, diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} exclude Exclude widgets from being moved. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function (col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row, + size_x: size_x, + size_y: size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), size_y ); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function (size_x, size_y) { + size_x || (size_x = 1); + size_y || (size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + var rows_l; + + for (var c = 1; c < cols_l; c++) { + rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x: size_x, + size_y: size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col: c, + row: r, + size_y: size_y, + size_x: size_x + }); + } + } + } + + if (valid_pos.length) { + return Gridster.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + fn.remove_by_grid = function (col, row) { + var $w = this.is_widget(col, row); + if ($w) { + this.remove_widget($w); + } + }; + + + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @param {Boolean|Function} [silent] If true, widgets below the removed one + * will not move up. If a Function is passed it will be used as callback. + * @param {Function} [callback] Function executed after the widget is removed. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function (el, silent, callback) { + var $el = el instanceof $ ? el : $(el); + if ($el.length === 0) { + //there is nothing to remove, so we can't remove it + return this; + } + var wgd = $el.coords().grid; + if (wgd === undefined) { + //there is no grid, so we can't remove it + return this; + } + + // if silent is a function assume it's a callback + if ($.isFunction(silent)) { + callback = silent; + silent = false; + } + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + this.options.hide_element.call(this, $el, $.proxy(function(){ + $el.remove(); + + if (!silent) { + $nexts.each($.proxy(function (i, widget) { + this.move_widget_up($(widget), wgd.size_y); + }, this)); + } + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + + return this; + }; + + + /** + * Remove all widgets from the grid. + * + * @method remove_all_widgets + * @param {Function} callback Function executed for each widget removed. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_all_widgets = function (callback) { + this.$widgets.each($.proxy(function (i, el) { + this.remove_widget(el, true, callback); + }, this)); + + return this; + }; + + + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function ($widgets) { + $widgets || ($widgets = this.$widgets); + var result = []; + $widgets.each($.proxy(function (i, widget) { + var $w = $(widget); + if (typeof($w.coords().grid) !== 'undefined') { + result.push(this.options.serialize_params($w, $w.coords().grid)); + } + }, this)); + return result; + }; + + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function () { + return this.serialize(this.$changed); + }; + + + /** + * Convert widgets from DOM elements to "widget grid data" Objects. + * + * @method dom_to_coords + * @param {HTMLElement} $widget The widget to be converted. + */ + fn.dom_to_coords = function ($widget) { + return { + 'col': parseInt($widget.attr('data-col'), 10), + 'row': parseInt($widget.attr('data-row'), 10), + 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1, + 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1, + 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false, + 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false, + 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false, + 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false, + 'el': $widget + }; + }; + + /** + * Creates the grid coords object representing the widget an add it to the + * mapped array of positions. + * + * @method register_widget + * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing + * the widget, or an "widget grid data" Object with (col, row, el ...). + * @return {Boolean} Returns true if the widget final position is different + * than the original. + */ + fn.register_widget = function ($el) { + var isDOM = $el instanceof $; + var wgd = isDOM ? this.dom_to_coords($el) : $el; + var posChanged = false; + isDOM || ($el = wgd.el); + + var empty_upper_row = this.can_go_widget_up(wgd); + if (this.options.shift_widgets_up && empty_upper_row) { + wgd.row = empty_upper_row; + $el.attr('data-row', empty_upper_row); + this.$el.trigger('gridster:positionchanged', [wgd]); + posChanged = true; + } + + if (this.options.avoid_overlapped_widgets && !this.can_move_to( + {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) + ) { + $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y)); + $el.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + posChanged = true; + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + this.update_widget_dimensions($el, wgd); + + this.options.resize.enabled && this.add_resize_handle($el); + + return posChanged; + }; + + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function (grid_data, value) { + this.for_each_cell_occupied(grid_data, function (col, row) { + if (!this.gridmap[col]) { + return this; + } + this.gridmap[col][row] = value; + }); + return this; + }; + + + /** + * Update the width and height for a widgets coordinate data. + * + * @param {HTMLElement} $widget The widget to update. + * @param wgd {Object} wgd Current widget grid data (col, row, size_x, size_y). + * @method update_widget_dimensions + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widget_dimensions = function ($widget, wgd) { + + var width = (wgd.size_x * (this.is_responsive() ? this.get_responsive_col_width() : this.options.widget_base_dimensions[0]) + + ((wgd.size_x - 1) * this.options.widget_margins[0])); + + var height = (wgd.size_y * this.options.widget_base_dimensions[1] + + ((wgd.size_y - 1) * this.options.widget_margins[1])); + + $widget.data('coords').update({ + width: width, + height: height + }); + + $widget.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + + return this; + }; + + + /** + * Update dimensions for all widgets in the grid. + * + * @method update_widgets_dimensions + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.update_widgets_dimensions = function () { + $.each(this.$widgets, $.proxy(function (idx, widget) { + var wgd = $(widget).coords().grid; + if (typeof (wgd) !== 'object') { + return; + } + this.update_widget_dimensions($(widget), wgd); + }, this)); + return this; + }; + + + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function (grid_data) { + return this.update_widget_position(grid_data, false); + }; + + + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} [value] The value to set in the specified + * position . + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function (grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + }; + + + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.draggable = function () { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left: this.options.widget_margins[0], + offset_top: this.options.widget_margins[1], + container_width: (this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0]), + limit: true, + start: function (event, ui) { + self.$widgets.filter('.player-revert') + .removeClass('player-revert'); + + self.$player = $(this); + self.$helper = $(ui.$helper); + + self.helper = !self.$helper.is(self.$player); + + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop: function (event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag: throttle(function (event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); + + //this.drag_api = this.$el.gridDraggable(draggable_options); + this.drag_api = this.$el.dragg(draggable_options).data('drag'); + }; + + + /** + * Bind resize events to get resize working. + * + * @method resizable + * @return {Gridster} Returns instance of gridster Class. + */ + fn.resizable = function () { + this.resize_api = this.$el.gridDraggable({ + items: '.' + this.options.resize.handle_class, + offset_left: this.options.widget_margins[0], + container_width: this.container_width, + move_element: false, + resize: true, + limit: this.options.max_cols !== Infinity, + scroll_container: this.options.scroll_container, + start: $.proxy(this.on_start_resize, this), + stop: $.proxy(function (event, ui) { + delay($.proxy(function () { + this.on_stop_resize(event, ui); + }, this), 120); + }, this), + drag: throttle($.proxy(this.on_resize, this), 60) + }); + + return this; + }; + + + /** + * Setup things required for resizing. Like build templates for drag handles. + * + * @method setup_resize + * @return {Gridster} Returns instance of gridster Class. + */ + fn.setup_resize = function () { + this.resize_handle_class = this.options.resize.handle_class; + var axes = this.options.resize.axes; + var handle_tpl = ''; + + this.resize_handle_tpl = $.map(axes, function (type) { + return handle_tpl.replace('{type}', type); + }).join(''); + + if ($.isArray(this.options.draggable.ignore_dragging)) { + this.options.draggable.ignore_dragging.push( + '.' + this.resize_handle_class); + } + + + return this; + }; + + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_drag = function (event, ui) { + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.highest_col = this.get_highest_occupied_cell().col; + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + this.set_dom_grid_height(this.$el.height() + + (this.player_grid_data.size_y * this.min_widget_height)); + + this.set_dom_grid_width(this.cols); + + var pgd_sizex = this.player_grid_data.size_x; + var cols_diff = this.cols - this.highest_col; + + if (this.options.max_cols === Infinity && cols_diff <= pgd_sizex) { + this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1)); + } + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied(this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied(this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + // see jquery.collision.js + this.collision_api = this.$helper.collision(colliders, this.options.collision); + + this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', { + 'class': 'preview-holder', + 'data-row': this.$player.attr('data-row'), + 'data-col': this.$player.attr('data-col'), + css: { + width: coords.width, + height: coords.height + } + }).appendTo(this.$el); + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_drag = function (event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + } + + var margin_sides = this.options.widget_margins; + + var placeholder_column = this.$preview_holder.attr('data-col'); + var placeholder_row = this.$preview_holder.attr('data-row'); + + var abs_offset = { + left: ui.position.left + this.baseX - (margin_sides[0] * placeholder_column), + top: ui.position.top + this.baseY - (margin_sides[1] * placeholder_row) + }; + + // auto grow cols + if (this.options.max_cols === Infinity) { + var prcol = this.placeholder_grid_data.col + + this.placeholder_grid_data.size_x - 1; + + // "- 1" due to adding at least 1 column in on_start_drag + if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) { + this.add_faux_cols(1); + this.set_dom_grid_width(this.cols + 1); + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + + this.collision_api.set_colliders(this.faux_grid); + } + + this.colliders_data = this.collision_api.get_closest_colliders(abs_offset); + + this.on_overlapped_column_change(this.on_start_overlapping_column, this.on_stop_overlapping_column); + + this.on_overlapped_row_change(this.on_start_overlapping_row, this.on_stop_overlapping_row); + + if (this.helper && this.$player) { + this.$player.css({ + 'left': ui.position.left, + 'top': ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_drag = function (event, ui) { + this.$helper.add(this.$player).add(this.$wrapper) + .removeClass('dragging'); + + var margin_sides = this.options.widget_margins; + + var placeholder_column = this.$preview_holder.attr('data-col'); + var placeholder_row = this.$preview_holder.attr('data-row'); + + ui.position.left = ui.position.left + this.baseX - (margin_sides[0] * placeholder_column); + ui.position.top = ui.position.top + this.baseY - (margin_sides[1] * placeholder_row); + this.colliders_data = this.collision_api.get_closest_colliders( + ui.position); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + this.$changed = this.$changed.add(this.$player); + + // move the cells down if there is an overlap and we are in static mode + if (this.options.collision.wait_for_mouseup) { + this.for_each_cell_occupied(this.placeholder_grid_data, function (tcol, trow) { + if (this.is_widget(tcol, trow)) { + this.move_widget_down(this.is_widget(tcol, trow), this.placeholder_grid_data.size_y); + } + }); + } + + this.cells_occupied_by_player = this.get_cells_occupied(this.placeholder_grid_data); + + var col = this.placeholder_grid_data.col; + var row = this.placeholder_grid_data.row; + + this.set_cells_player_occupies(col, row); + this.$player.coords().grid.row = row; + this.$player.coords().grid.col = col; + + this.$player.addClass('player-revert').removeClass('player') + .attr({ + 'data-col': col, + 'data-row': row + }).css({ + 'left': '', + 'top': '' + }); + + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); + } + + this.$preview_holder.remove(); + + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + this.w_queue = {}; + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (this.options.max_cols === Infinity) { + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + }; + + + /** + * This function is executed every time a widget starts to be resized. + * + * @method on_start_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_start_resize = function (event, ui) { + this.$resized_widget = ui.$player.closest('.gs-w'); + this.resize_coords = this.$resized_widget.coords(); + this.resize_wgd = this.resize_coords.grid; + this.resize_initial_width = this.resize_coords.coords.width; + this.resize_initial_height = this.resize_coords.coords.height; + this.resize_initial_sizex = this.resize_coords.grid.size_x; + this.resize_initial_sizey = this.resize_coords.grid.size_y; + this.resize_initial_col = this.resize_coords.grid.col; + this.resize_last_sizex = this.resize_initial_sizex; + this.resize_last_sizey = this.resize_initial_sizey; + + this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x || this.options.resize.max_size[0], + this.options.max_cols - this.resize_initial_col + 1); + this.resize_max_size_y = this.resize_wgd.max_size_y || this.options.resize.max_size[1]; + + this.resize_min_size_x = (this.resize_wgd.min_size_x || this.options.resize.min_size[0] || 1); + this.resize_min_size_y = (this.resize_wgd.min_size_y || this.options.resize.min_size[1] || 1); + + this.resize_initial_last_col = this.get_highest_occupied_cell().col; + + this.set_dom_grid_width(this.cols); + + this.resize_dir = { + right: ui.$player.is('.' + this.resize_handle_class + '-x'), + bottom: ui.$player.is('.' + this.resize_handle_class + '-y') + }; + + if (!this.is_responsive()) { + this.$resized_widget.css({ + 'min-width': this.options.widget_base_dimensions[0], + 'min-height': this.options.widget_base_dimensions[1] + }); + } + + var nodeName = this.$resized_widget.get(0).tagName; + this.$resize_preview_holder = $('<' + nodeName + ' />', { + 'class': 'preview-holder resize-preview-holder', + 'data-row': this.$resized_widget.attr('data-row'), + 'data-col': this.$resized_widget.attr('data-col'), + 'css': { + 'width': this.resize_initial_width, + 'height': this.resize_initial_height + } + }).appendTo(this.$el); + + this.$resized_widget.addClass('resizing'); if (this.options.resize.start) { - this.options.resize.start.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resizestart'); - }; - - - /** - * This function is executed every time a widget stops being resized. - * - * @method on_stop_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_stop_resize = function(event, ui) { - this.$resized_widget - .removeClass('resizing') - .css({ - 'width': '', - 'height': '' - }); - - delay($.proxy(function() { - this.$resize_preview_holder - .remove() - .css({ - 'min-width': '', - 'min-height': '' - }); - - if (this.options.resize.stop) { - this.options.resize.stop.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resizestop'); - }, this), 300); - - this.set_dom_grid_width(); - - if (this.options.autogrow_cols) { - this.drag_api.set_limits(this.cols * this.min_widget_width); - } - }; - - - /** - * This function is executed when a widget is being resized. - * - * @method on_resize - * @param {Event} event The original browser event - * @param {Object} ui A prepared ui object with useful drag-related data - */ - fn.on_resize = function(event, ui) { - var rel_x = (ui.pointer.diff_left); - var rel_y = (ui.pointer.diff_top); - var wbd_x = this.options.widget_base_dimensions[0]; - var wbd_y = this.options.widget_base_dimensions[1]; - var margin_x = this.options.widget_margins[0]; - var margin_y = this.options.widget_margins[1]; - var max_size_x = this.resize_max_size_x; - var min_size_x = this.resize_min_size_x; - var max_size_y = this.resize_max_size_y; - var min_size_y = this.resize_min_size_y; - var autogrow = this.options.autogrow_cols; - var width; - var max_width = Infinity; - var max_height = Infinity; - - var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); - var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); - - var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); - var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); - - var max_cols = (this.container_width / this.min_widget_width) - - this.resize_initial_col + 1; - var limit_width = ((max_cols * this.min_widget_width) - margin_x * 2); - - size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); - size_x = Math.min(max_cols, size_x); - width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); - max_width = Math.min(width, limit_width); - min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x * 2); - - size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); - max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); - min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y * 2); - - if (this.resize_dir.right) { - size_y = this.resize_initial_sizey; - } else if (this.resize_dir.bottom) { - size_x = this.resize_initial_sizex; - } - - if (autogrow) { - var last_widget_col = this.resize_initial_col + size_x - 1; - if (autogrow && this.resize_initial_last_col <= last_widget_col) { - this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); - - if (this.cols < last_widget_col) { - this.add_faux_cols(last_widget_col - this.cols); - } - } - } - - var css_props = {}; - !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( - this.resize_initial_width + rel_x, max_width), min_width)); - !this.resize_dir.right && (css_props.height = Math.max(Math.min( - this.resize_initial_height + rel_y, max_height), min_height)); - - this.$resized_widget.css(css_props); - - if (size_x !== this.resize_last_sizex || - size_y !== this.resize_last_sizey) { - - this.resize_widget(this.$resized_widget, size_x, size_y); - this.set_dom_grid_width(this.cols); - - this.$resize_preview_holder.css({ - 'width': '', - 'height': '' - }).attr({ - 'data-row': this.$resized_widget.attr('data-row'), - 'data-sizex': size_x, - 'data-sizey': size_y - }); - } - - if (this.options.resize.resize) { - this.options.resize.resize.call(this, event, ui, this.$resized_widget); - } - - this.$el.trigger('gridster:resize'); - - this.resize_last_sizex = size_x; - this.resize_last_sizey = size_y; - }; - - - /** - * Executes the callbacks passed as arguments when a column begins to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new column - * begins to be overlapped. The column is passed as first argument. - * @param {Function} stop_callback Function executed when a column stops - * being overlapped. The column is passed as first argument. - * @method on_overlapped_column_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_column_change = function(start_callback, stop_callback) { - if (!this.colliders_data.length) { - return this; - } - var cols = this.get_targeted_columns( - this.colliders_data[0].el.data.col); - - var last_n_cols = this.last_cols.length; - var n_cols = cols.length; - var i; - - for (i = 0; i < n_cols; i++) { - if ($.inArray(cols[i], this.last_cols) === -1) { - (start_callback || $.noop).call(this, cols[i]); - } - } - - for (i = 0; i< last_n_cols; i++) { - if ($.inArray(this.last_cols[i], cols) === -1) { - (stop_callback || $.noop).call(this, this.last_cols[i]); - } - } - - this.last_cols = cols; - - return this; - }; - - - /** - * Executes the callbacks passed as arguments when a row starts to be - * overlapped or stops being overlapped. - * - * @param {Function} start_callback Function executed when a new row begins - * to be overlapped. The row is passed as first argument. - * @param {Function} end_callback Function executed when a row stops being - * overlapped. The row is passed as first argument. - * @method on_overlapped_row_change - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.on_overlapped_row_change = function(start_callback, end_callback) { - if (!this.colliders_data.length) { - return this; - } - var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); - var last_n_rows = this.last_rows.length; - var n_rows = rows.length; - var i; - - for (i = 0; i < n_rows; i++) { - if ($.inArray(rows[i], this.last_rows) === -1) { - (start_callback || $.noop).call(this, rows[i]); - } - } - - for (i = 0; i < last_n_rows; i++) { - if ($.inArray(this.last_rows[i], rows) === -1) { - (end_callback || $.noop).call(this, this.last_rows[i]); - } - } - - this.last_rows = rows; - }; - - - /** - * Sets the current position of the player - * - * @param {Number} col - * @param {Number} row - * @param {Boolean} no_player - * @method set_player - * @return {object} - */ - fn.set_player = function(col, row, no_player) { - var self = this; - if (!no_player) { - this.empty_cells_player_occupies(); - } - var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; - var to_col = cell.col; - var to_row = row || cell.row; - - this.player_grid_data = { - col: to_col, - row: to_row, - size_y : this.player_grid_data.size_y, - size_x : this.player_grid_data.size_x - }; - - this.cells_occupied_by_player = this.get_cells_occupied( - this.player_grid_data); - - var $overlapped_widgets = this.get_widgets_overlapped( - this.player_grid_data); - - var constraints = this.widgets_constraints($overlapped_widgets); - - this.manage_movements(constraints.can_go_up, to_col, to_row); - this.manage_movements(constraints.can_not_go_up, to_col, to_row); - - /* if there is not widgets overlapping in the new player position, - * update the new placeholder position. */ - if (!$overlapped_widgets.length) { - var pp = this.can_go_player_up(this.player_grid_data); - if (pp !== false) { - to_row = pp; - } - this.set_placeholder(to_col, to_row); - } - - return { - col: to_col, - row: to_row - }; - }; - - - /** - * See which of the widgets in the $widgets param collection can go to - * a upper row and which not. - * - * @method widgets_contraints - * @param {jQuery} $widgets A jQuery wrapped collection of - * HTMLElements. - * @return {object} Returns a literal Object with two keys: `can_go_up` & - * `can_not_go_up`. Each contains a set of HTMLElements. - */ - fn.widgets_constraints = function($widgets) { - var $widgets_can_go_up = $([]); - var $widgets_can_not_go_up; - var wgd_can_go_up = []; - var wgd_can_not_go_up = []; - - $widgets.each($.proxy(function(i, w) { - var $w = $(w); - var wgd = $w.coords().grid; - if (this.can_go_widget_up(wgd)) { - $widgets_can_go_up = $widgets_can_go_up.add($w); - wgd_can_go_up.push(wgd); - } else { - wgd_can_not_go_up.push(wgd); - } - }, this)); - - $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); - - return { - can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), - can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) - }; - }; - - - /** - * Sorts an Array of grid coords objects (representing the grid coords of - * each widget) in descending way. - * - * @method manage_movements - * @param {jQuery} $widgets A jQuery collection of HTMLElements - * representing the widgets you want to move. - * @param {Number} to_col The column to which we want to move the widgets. - * @param {Number} to_row The row to which we want to move the widgets. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.manage_movements = function($widgets, to_col, to_row) { - $.each($widgets, $.proxy(function(i, w) { - var wgd = w; - var $w = wgd.el; - - var can_go_widget_up = this.can_go_widget_up(wgd); - - if (can_go_widget_up) { - //target CAN go up - //so move widget up - this.move_widget_to($w, can_go_widget_up); - this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); - - } else { - //target can't go up - var can_go_player_up = this.can_go_player_up( - this.player_grid_data); - - if (!can_go_player_up) { - // target can't go up - // player cant't go up - // so we need to move widget down to a position that dont - // overlaps player - var y = (to_row + this.player_grid_data.size_y) - wgd.row; - - this.move_widget_down($w, y); - this.set_placeholder(to_col, to_row); - } - } - }, this)); - - return this; - }; - - /** - * Determines if there is a widget in the row and col given. Or if the - * HTMLElement passed as first argument is the player. - * - * @method is_player - * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of - * HTMLElements. - * @param {Number} [row] The column to which we want to move the widgets. - * @return {Boolean} Returns true or false. - */ - fn.is_player = function(col_or_el, row) { - if (row && !this.gridmap[col_or_el]) { return false; } - var $w = row ? this.gridmap[col_or_el][row] : col_or_el; - return $w && ($w.is(this.$player) || $w.is(this.$helper)); - }; - - - /** - * Determines if the widget that is being dragged is currently over the row - * and col given. - * - * @method is_player_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_player_in = function(col, row) { - var c = this.cells_occupied_by_player || {}; - return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; - }; - - - /** - * Determines if the placeholder is currently over the row and col given. - * - * @method is_placeholder_in - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in = function(col, row) { - var c = this.cells_occupied_by_placeholder || {}; - return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; - }; - - - /** - * Determines if the placeholder is currently over the column given. - * - * @method is_placeholder_in_col - * @param {Number} col The column to check. - * @return {Boolean} Returns true or false. - */ - fn.is_placeholder_in_col = function(col) { - var c = this.cells_occupied_by_placeholder || []; - return $.inArray(col, c.cols) >= 0; - }; - - - /** - * Determines if the cell represented by col and row params is empty. - * - * @method is_empty - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_empty = function(col, row) { - if (typeof this.gridmap[col] !== 'undefined') { - if(typeof this.gridmap[col][row] !== 'undefined' && - this.gridmap[col][row] === false + this.options.resize.start.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestart'); + }; + + + /** + * This function is executed every time a widget stops being resized. + * + * @method on_stop_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_stop_resize = function (event, ui) { + this.$resized_widget + .removeClass('resizing') + .css({ + 'width': '', + 'height': '', + 'min-width': '', + 'min-height': '' + }); + + delay($.proxy(function () { + this.$resize_preview_holder + .remove() + .css({ + 'min-width': '', + 'min-height': '' + }); + + if (this.options.resize.stop) { + this.options.resize.stop.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resizestop'); + }, this), 300); + + this.set_dom_grid_width(); + this.set_dom_grid_height(); + + if (this.options.max_cols === Infinity) { + this.drag_api.set_limits(this.cols * this.min_widget_width); + } + }; + + + /** + * This function is executed when a widget is being resized. + * + * @method on_resize + * @param {Event} event The original browser event + * @param {Object} ui A prepared ui object with useful drag-related data + */ + fn.on_resize = function (event, ui) { + var rel_x = (ui.pointer.diff_left); + var rel_y = (ui.pointer.diff_top); + var wbd_x = this.is_responsive() ? this.get_responsive_col_width() : this.options.widget_base_dimensions[0]; + var wbd_y = this.options.widget_base_dimensions[1]; + var margin_x = this.options.widget_margins[0]; + var margin_y = this.options.widget_margins[1]; + var max_size_x = this.resize_max_size_x; + var min_size_x = this.resize_min_size_x; + var max_size_y = this.resize_max_size_y; + var min_size_y = this.resize_min_size_y; + var autogrow = this.options.max_cols === Infinity; + var width; + + var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2); + var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2); + + var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x); + var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y); + + // Max number of cols this widget can be in width + var max_cols = Math.floor((this.container_width / this.min_widget_width) - this.resize_initial_col + 1); + + var limit_width = (max_cols * this.min_widget_width) + ((max_cols - 1) * margin_x); + + size_x = Math.max(Math.min(size_x, max_size_x), min_size_x); + size_x = Math.min(max_cols, size_x); + width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x ); + var max_width = Math.min(width, limit_width); + var min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x); + + size_y = Math.max(Math.min(size_y, max_size_y), min_size_y); + var max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y); + var min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y); + + if (this.resize_dir.right) { + size_y = this.resize_initial_sizey; + } else if (this.resize_dir.bottom) { + size_x = this.resize_initial_sizex; + } + + if (autogrow) { + var last_widget_col = this.resize_initial_col + size_x - 1; + if (autogrow && this.resize_initial_last_col <= last_widget_col) { + this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols)); + + if (this.cols < last_widget_col) { + this.add_faux_cols(last_widget_col - this.cols); + } + } + } + + var css_props = {}; + !this.resize_dir.bottom && (css_props.width = Math.max(Math.min( + this.resize_initial_width + rel_x, max_width), min_width)); + !this.resize_dir.right && (css_props.height = Math.max(Math.min( + this.resize_initial_height + rel_y, max_height), min_height)); + + this.$resized_widget.css(css_props); + + if (size_x !== this.resize_last_sizex || + size_y !== this.resize_last_sizey) { + + this.resize_widget(this.$resized_widget, size_x, size_y, false); + this.set_dom_grid_width(this.cols); + + this.$resize_preview_holder.css({ + 'width': '', + 'height': '' + }).attr({ + 'data-row': this.$resized_widget.attr('data-row'), + 'data-sizex': size_x, + 'data-sizey': size_y + }); + } + + if (this.options.resize.resize) { + this.options.resize.resize.call(this, event, ui, this.$resized_widget); + } + + this.$el.trigger('gridster:resize'); + + this.resize_last_sizex = size_x; + this.resize_last_sizey = size_y; + }; + + + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function (start_callback, stop_callback) { + if (!this.colliders_data.length) { + return this; + } + var cols = this.get_targeted_columns( + this.colliders_data[0].el.data.col); + + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for (i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for (i = 0; i < last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; + }; + + + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} end_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function (start_callback, end_callback) { + if (!this.colliders_data.length) { + return this; + } + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; + + for (i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } + + for (i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; + }; + + + /** + * Sets the current position of the player + * + * @param {Number} col + * @param {Number} row + * @param {Boolean} no_player + * @method set_player + * @return {object} + */ + fn.set_player = function (col, row, no_player) { + var self = this; + var swap = false; + if (!no_player) { + this.empty_cells_player_occupies(); + } + var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; + var to_col = cell.col; + var to_row = cell.row || row; + + this.player_grid_data = { + col: to_col, + row: to_row, + size_y: this.player_grid_data.size_y, + size_x: this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + + //Added placeholder for more advanced movement. + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped(this.player_grid_data); + + var player_size_y = this.player_grid_data.size_y; + var player_size_x = this.player_grid_data.size_x; + var placeholder_cells = this.cells_occupied_by_placeholder; + var $gr = this; + + + //Queue Swaps + $overlapped_widgets.each($.proxy(function (i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + var outside_col = placeholder_cells.cols[0] + player_size_x - 1; + var outside_row = placeholder_cells.rows[0] + player_size_y - 1; + if ($w.hasClass($gr.options.static_class)) { + //next iteration + return true; + } + if ($gr.options.collision.wait_for_mouseup && $gr.drag_api.is_dragging){ + //skip the swap and just 'move' the place holder + $gr.placeholder_grid_data.col = to_col; + $gr.placeholder_grid_data.row = to_row; + + $gr.cells_occupied_by_placeholder = $gr.get_cells_occupied( + $gr.placeholder_grid_data); + + $gr.$preview_holder.attr({ + 'data-row': to_row, + 'data-col': to_col + }); + } else if (wgd.size_x <= player_size_x && wgd.size_y <= player_size_y) { + if (!$gr.is_swap_occupied(placeholder_cells.cols[0], wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], wgd.row) && !$gr.is_in_queue(placeholder_cells.cols[0], wgd.row, $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], wgd.row, $w); + } + else if (!$gr.is_swap_occupied(outside_col, wgd.row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(outside_col, wgd.row) && !$gr.is_in_queue(outside_col, wgd.row, $w)) { + swap = $gr.queue_widget(outside_col, wgd.row, $w); + } + else if (!$gr.is_swap_occupied(wgd.col, placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, placeholder_cells.rows[0]) && !$gr.is_in_queue(wgd.col, placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(wgd.col, placeholder_cells.rows[0], $w); + } + else if (!$gr.is_swap_occupied(wgd.col, outside_row, wgd.size_x, wgd.size_y) && !$gr.is_player_in(wgd.col, outside_row) && !$gr.is_in_queue(wgd.col, outside_row, $w)) { + swap = $gr.queue_widget(wgd.col, outside_row, $w); + } + else if (!$gr.is_swap_occupied(placeholder_cells.cols[0], placeholder_cells.rows[0], wgd.size_x, wgd.size_y) && !$gr.is_player_in(placeholder_cells.cols[0], placeholder_cells.rows[0]) && !$gr.is_in_queue(placeholder_cells.cols[0], placeholder_cells.rows[0], $w)) { + swap = $gr.queue_widget(placeholder_cells.cols[0], placeholder_cells.rows[0], $w); + } else { + //in one last attempt we check for any other empty spaces + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = placeholder_cells.cols[0] + c; + var rowc = placeholder_cells.rows[0] + r; + if (!$gr.is_swap_occupied(colc, rowc, wgd.size_x, wgd.size_y) && !$gr.is_player_in(colc, rowc) && !$gr.is_in_queue(colc, rowc, $w)) { + swap = $gr.queue_widget(colc, rowc, $w); + c = player_size_x; + break; + } + } + } + + } + } else if ($gr.options.shift_larger_widgets_down && !swap) { + $overlapped_widgets.each($.proxy(function (i, w) { + var $w = $(w); + + if ($gr.can_go_down($w) && $w.coords().grid.row === $gr.player_grid_data.row) { + $gr.move_widget_down($w, $gr.player_grid_data.size_y); + $gr.set_placeholder(to_col, to_row); + } + })); + } + + $gr.clean_up_changed(); + })); + + //Move queued widgets + if (swap && this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + for (var key in this.w_queue) { + var _col = parseInt(key.split('_')[0]); + var _row = parseInt(key.split('_')[1]); + if (this.w_queue[key] !== 'full') { + this.new_move_widget_to(this.w_queue[key], _col, _row); + } + } + this.set_placeholder(to_col, to_row); + } + + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + if (this.options.shift_widgets_up) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + } + if (this.can_placeholder_be_set(to_col, to_row, player_size_x, player_size_y)) { + this.set_placeholder(to_col, to_row); + } + } + + this.w_queue = {}; + + return { + col: to_col, + row: to_row + }; + }; + + + fn.is_swap_occupied = function (col, row, w_size_x, w_size_y) { + var occupied = false; + for (var c = 0; c < w_size_x; c++) { + for (var r = 0; r < w_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + '_' + rowc; + if (this.is_occupied(colc, rowc)) { + occupied = true; + } else if (key in this.w_queue) { + if (this.w_queue[key] === 'full') { + occupied = true; + continue; + } + var $tw = this.w_queue[key]; + var tgd = $tw.coords().grid; + //remove queued items if no longer under player. + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + } + } + if (rowc > parseInt(this.options.max_rows)) { + occupied = true; + } + if (colc > parseInt(this.options.max_cols)) { + occupied = true; + } + if (this.is_player_in(colc, rowc)) { + occupied = true; + } + } + } + + return occupied; + }; + + fn.can_placeholder_be_set = function (col, row, player_size_x, player_size_y) { + var can_set = true; + for (var c = 0; c < player_size_x; c++) { + for (var r = 0; r < player_size_y; r++) { + var colc = col + c; + var rowc = row + r; + var $tw = this.is_widget(colc, rowc); + //if this space is occupied and not queued for move. + if (rowc > parseInt(this.options.max_rows)) { + can_set = false; + } + if (colc > parseInt(this.options.max_cols)) { + can_set = false; + } + if (this.is_occupied(colc, rowc) && !this.is_widget_queued_and_can_move($tw)) { + can_set = false; + } + } + } + return can_set; + }; + + fn.queue_widget = function (col, row, $widget) { + var $w = $widget; + var wgd = $w.coords().grid; + var primary_key = col + '_' + row; + if (primary_key in this.w_queue) { + return false; + } + + this.w_queue[primary_key] = $w; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = col + c; + var rowc = row + r; + var key = colc + '_' + rowc; + if (key === primary_key) { + continue; + } + this.w_queue[key] = 'full'; + } + } + + return true; + }; + + fn.is_widget_queued_and_can_move = function ($widget) { + var queued = false; + if ($widget === false) { + return false; + } + + for (var key in this.w_queue) { + if (this.w_queue[key] === 'full') { + continue; + } + if (this.w_queue[key].attr('data-col') === $widget.attr('data-col') && this.w_queue[key].attr('data-row') === $widget.attr('data-row')) { + queued = true; + //test whole space + var $w = this.w_queue[key]; + var dcol = parseInt(key.split('_')[0]); + var drow = parseInt(key.split('_')[1]); + var wgd = $w.coords().grid; + + for (var c = 0; c < wgd.size_x; c++) { + for (var r = 0; r < wgd.size_y; r++) { + var colc = dcol + c; + var rowc = drow + r; + if (this.is_player_in(colc, rowc)) { + queued = false; + } + + } + } + + } + } + + return queued; + }; + + fn.is_in_queue = function (col, row, $widget) { + var queued = false; + var key = col + '_' + row; + + if ((key in this.w_queue)) { + if (this.w_queue[key] === 'full') { + queued = true; + } else { + var $tw = this.w_queue[key]; + var tgd = $tw.coords().grid; + if (!this.is_widget_under_player(tgd.col, tgd.row)) { + delete this.w_queue[key]; + queued = false; + } else if (this.w_queue[key].attr('data-col') === $widget.attr('data-col') && this.w_queue[key].attr('data-row') === $widget.attr('data-row')) { + delete this.w_queue[key]; + queued = false; + } else { + queued = true; + } + } + } + + return queued; + }; + + + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {jQuery} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {object} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function ($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; + + $widgets.each($.proxy(function (i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + } else { + wgd_can_not_go_up.push(wgd); + } + }, this)); + + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + + return { + can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up), + can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up) + }; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + + * Depreciated. + * + * @method manage_movements + * @param {jQuery} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function ($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function (i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up( + this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + if (this.can_go_down($w)) { + console.log('In Move Down!'); + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + } + }, this)); + + return this; + }; + + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function (col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { + return false; + } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; + + + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function (col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function (col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function (col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; + + + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function (col, row) { + if (typeof this.gridmap[col] !== 'undefined') { + if (typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false ) { return true; } return false; } return true; - }; - - - /** - * Determines if the cell represented by col and row params is occupied. - * - * @method is_occupied - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_occupied = function(col, row) { - if (!this.gridmap[col]) { - return false; - } - - if (this.gridmap[col][row]) { - return true; - } - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row params. - * - * @method is_widget - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean|HTMLElement} Returns false if there is no widget, - * else returns the jQuery HTMLElement - */ - fn.is_widget = function(col, row) { - var cell = this.gridmap[col]; - if (!cell) { - return false; - } - - cell = cell[row]; - - if (cell) { - return cell; - } - - return false; - }; - - - /** - * Determines if there is a widget in the cell represented by col/row - * params and if this is under the widget that is being dragged. - * - * @method is_widget_under_player - * @param {Number} col The column to check. - * @param {Number} row The row to check. - * @return {Boolean} Returns true or false. - */ - fn.is_widget_under_player = function(col, row) { - if (this.is_widget(col, row)) { - return this.is_player_in(col, row); - } - return false; - }; - - - /** - * Get widgets overlapping with the player or with the object passed - * representing the grid cells. - * - * @method get_widgets_under_player - * @return {HTMLElement} Returns a jQuery collection of HTMLElements - */ - fn.get_widgets_under_player = function(cells) { - cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); - var $widgets = $([]); - - $.each(cells.cols, $.proxy(function(i, col) { - $.each(cells.rows, $.proxy(function(i, row) { - if(this.is_widget(col, row)) { - $widgets = $widgets.add(this.gridmap[col][row]); - } - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * Put placeholder at the row and column specified. - * - * @method set_placeholder - * @param {Number} col The column to which we want to move the - * placeholder. - * @param {Number} row The row to which we want to move the - * placeholder. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_placeholder = function(col, row) { - var phgd = $.extend({}, this.placeholder_grid_data); - var $nexts = this.widgets_below({ - col: phgd.col, - row: phgd.row, - size_y: phgd.size_y, - size_x: phgd.size_x - }); - - // Prevents widgets go out of the grid - var right_col = (col + phgd.size_x - 1); - if (right_col > this.cols) { - col = col - (right_col - col); - } - - var moved_down = this.placeholder_grid_data.row < row; - var changed_column = this.placeholder_grid_data.col !== col; - - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - - this.cells_occupied_by_placeholder = this.get_cells_occupied( - this.placeholder_grid_data); - - this.$preview_holder.attr({ - 'data-row' : row, - 'data-col' : col - }); - - if (moved_down || changed_column) { - $nexts.each($.proxy(function(i, widget) { - this.move_widget_up( - $(widget), this.placeholder_grid_data.col - col + phgd.size_y); - }, this)); - } - - var $widgets_under_ph = this.get_widgets_under_player( - this.cells_occupied_by_placeholder); - - if ($widgets_under_ph.length) { - $widgets_under_ph.each($.proxy(function(i, widget) { - var $w = $(widget); - this.move_widget_down( - $w, row + phgd.size_y - $w.data('coords').grid.row); - }, this)); - } - - }; - - - /** - * Determines whether the player can move to a position above. - * - * @method can_go_player_up - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @return {Number|Boolean} If the player can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_player_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - var $widgets_under_player = this.get_widgets_under_player(); - - /* generate an array with columns as index and array with upper rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - var r = p_bottom_row + 1; - upper_rows[tcol] = []; - - while (--r > 0) { - if (this.is_empty(tcol, r) || this.is_player(tcol, r) || - this.is_widget(tcol, r) && - grid_col[r].is($widgets_under_player) - ) { - upper_rows[tcol].push(r); - min_row = r < min_row ? r : min_row; - } else { - break; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(function(a, b) { - return a - b; - }); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Determines whether a widget can move to a position above. - * - * @method can_go_widget_up - * @param {Object} widget_grid_data The actual grid coords object of the - * widget we want to check. - * @return {Number|Boolean} If the widget can be moved to an upper row - * returns the row number, else returns false. - */ - fn.can_go_widget_up = function(widget_grid_data) { - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var result = true; - var upper_rows = []; - var min_row = 10000; - - /* generate an array with columns as index and array with topmost rows - * empty as value */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = this.gridmap[tcol]; - upper_rows[tcol] = []; - - var r = p_bottom_row + 1; - // iterate over each row - while (--r > 0) { - if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { - if (!grid_col[r].is(widget_grid_data.el)) { - break; - } - } - - if (!this.is_player(tcol, r) && - !this.is_placeholder_in(tcol, r) && - !this.is_player_in(tcol, r)) { - upper_rows[tcol].push(r); - } - - if (r < min_row) { - min_row = r; - } - } - - if (upper_rows[tcol].length === 0) { - result = false; - return true; //break - } - - upper_rows[tcol].sort(function(a, b) { - return a - b; - }); - }); - - if (!result) { return false; } - - return this.get_valid_rows(widget_grid_data, upper_rows, min_row); - }; - - - /** - * Search a valid row for the widget represented by `widget_grid_data' in - * the `upper_rows` array. Iteration starts from row specified in `min_row`. - * - * @method get_valid_rows - * @param {Object} widget_grid_data The actual grid coords object of the - * player. - * @param {Array} upper_rows An array with columns as index and arrays - * of valid rows as values. - * @param {Number} min_row The upper row from which the iteration will start. - * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` - * for the widget in question. - */ - fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { - var p_top_row = widget_grid_data.row; - var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; - var size_y = widget_grid_data.size_y; - var r = min_row - 1; - var valid_rows = []; - - while (++r <= p_bottom_row ) { - var common = true; - $.each(upper_rows, function(col, rows) { - if ($.isArray(rows) && $.inArray(r, rows) === -1) { - common = false; - } - }); - - if (common === true) { - valid_rows.push(r); - if (valid_rows.length === size_y) { - break; - } - } - } - - var new_row = false; - if (size_y === 1) { - if (valid_rows[0] !== p_top_row) { - new_row = valid_rows[0] || false; - } - } else { - if (valid_rows[0] !== p_top_row) { - new_row = this.get_consecutive_numbers_index( - valid_rows, size_y); - } - } - - return new_row; - }; - - - fn.get_consecutive_numbers_index = function(arr, size_y) { - var max = arr.length; - var result = []; - var first = true; - var prev = -1; // or null? - - for (var i=0; i < max; i++) { - if (first || arr[i] === prev + 1) { - result.push(i); - if (result.length === size_y) { - break; - } - first = false; - } else { - result = []; - first = true; - } - - prev = arr[i]; - } - - return result.length >= size_y ? arr[result[0]] : false; - }; - - - /** - * Get widgets overlapping with the player. - * - * @method get_widgets_overlapped - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.get_widgets_overlapped = function() { - var $w; - var $widgets = $([]); - var used = []; - var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); - rows_from_bottom.reverse(); - - $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { - $.each(rows_from_bottom, $.proxy(function(i, row) { - // if there is a widget in the player position - if (!this.gridmap[col]) { return true; } //next iteration - var $w = this.gridmap[col][row]; - if (this.is_occupied(col, row) && !this.is_player($w) && - $.inArray($w, used) === -1 - ) { - $widgets = $widgets.add($w); - used.push($w); - } - - }, this)); - }, this)); - - return $widgets; - }; - - - /** - * This callback is executed when the player begins to collide with a column. - * - * @method on_start_overlapping_column - * @param {Number} col The collided column. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_column = function(col) { - this.set_player(col, false); - }; - - - /** - * A callback executed when the player begins to collide with a row. - * - * @method on_start_overlapping_row - * @param {Number} row The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_start_overlapping_row = function(row) { - this.set_player(false, row); - }; - - - /** - * A callback executed when the the player ends to collide with a column. - * - * @method on_stop_overlapping_column - * @param {Number} col The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_column = function(col) { - this.set_player(col, false); - - var self = this; - this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], - function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - }; - - - /** - * This callback is executed when the player ends to collide with a row. - * - * @method on_stop_overlapping_row - * @param {Number} row The collided row. - * @return {jQuery} Returns a jQuery collection of HTMLElements. - */ - fn.on_stop_overlapping_row = function(row) { - this.set_player(false, row); - - var self = this; - var cols = this.cells_occupied_by_player.cols; - for (var c = 0, cl = cols.length; c < cl; c++) { - this.for_each_widget_below(cols[c], row, function(tcol, trow) { - self.move_widget_up(this, self.player_grid_data.size_y); - }); - } - }; - - - /** - * Move a widget to a specific row. The cell or cells must be empty. - * If the widget has widgets below, all of these widgets will be moved also - * if they can. - * - * @method move_widget_to - * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the - * widget is going to be moved. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_to = function($widget, row) { - var self = this; - var widget_grid_data = $widget.coords().grid; - var diff = row - widget_grid_data.row; - var $next_widgets = this.widgets_below($widget); - - var can_move_to_new_cell = this.can_move_to( - widget_grid_data, widget_grid_data.col, row, $widget); - - if (can_move_to_new_cell === false) { - return false; - } - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', row); - this.$changed = this.$changed.add($widget); - - - $next_widgets.each(function(i, widget) { - var $w = $(widget); - var wgd = $w.coords().grid; - var can_go_up = self.can_go_widget_up(wgd); - if (can_go_up && can_go_up !== wgd.row) { - self.move_widget_to($w, can_go_up); - } - }); - - return this; - }; - - - /** - * Move up the specified widget and all below it. - * - * @method move_widget_up - * @param {HTMLElement} $widget The widget you want to move. - * @param {Number} [y_units] The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_up = function($widget, y_units) { - var el_grid_data = $widget.coords().grid; - var actual_row = el_grid_data.row; - var moved = []; - var can_go_up = true; - y_units || (y_units = 1); - - if (!this.can_go_up($widget)) { return false; } //break; - - this.for_each_column_occupied(el_grid_data, function(col) { - // can_go_up - if ($.inArray($widget, moved) === -1) { - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row - y_units; - next_row = this.can_go_up_to_row( - widget_grid_data, col, next_row); - - if (!next_row) { - return true; - } - - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - widget_grid_data.row = next_row; - this.add_to_gridmap(widget_grid_data); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); - - moved.push($widget); - - $next_widgets.each($.proxy(function(i, widget) { - this.move_widget_up($(widget), y_units); - }, this)); - } - }); - - }; - - - /** - * Move down the specified widget and all below it. - * - * @method move_widget_down - * @param {jQuery} $widget The jQuery object representing the widget - * you want to move. - * @param {Number} y_units The number of cells that the widget has to move. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.move_widget_down = function($widget, y_units) { - var el_grid_data, actual_row, moved, y_diff; - - if (y_units <= 0) { return false; } - - el_grid_data = $widget.coords().grid; - actual_row = el_grid_data.row; - moved = []; - y_diff = y_units; - - if (!$widget) { return false; } - - if ($.inArray($widget, moved) === -1) { - - var widget_grid_data = $widget.coords().grid; - var next_row = actual_row + y_units; - var $next_widgets = this.widgets_below($widget); - - this.remove_from_gridmap(widget_grid_data); - - $next_widgets.each($.proxy(function(i, widget) { - var $w = $(widget); - var wd = $w.coords().grid; - var tmp_y = this.displacement_diff( - wd, widget_grid_data, y_diff); - - if (tmp_y > 0) { - this.move_widget_down($w, tmp_y); - } - }, this)); - - widget_grid_data.row = next_row; - this.update_widget_position(widget_grid_data, $widget); - $widget.attr('data-row', widget_grid_data.row); - this.$changed = this.$changed.add($widget); - - moved.push($widget); - } - }; - - - /** - * Check if the widget can move to the specified row, else returns the - * upper row possible. - * - * @method can_go_up_to_row - * @param {Number} widget_grid_data The current grid coords object of the - * widget. - * @param {Number} col The target column. - * @param {Number} row The target row. - * @return {Boolean|Number} Returns the row number if the widget can move - * to the target position, else returns false. - */ - fn.can_go_up_to_row = function(widget_grid_data, col, row) { - var ga = this.gridmap; - var result = true; - var urc = []; // upper_rows_in_columns - var actual_row = widget_grid_data.row; - var r; - - /* generate an array with columns as index and array with - * upper rows empty in the column */ - this.for_each_column_occupied(widget_grid_data, function(tcol) { - var grid_col = ga[tcol]; - urc[tcol] = []; - - r = actual_row; - while (r--) { - if (this.is_empty(tcol, r) && - !this.is_placeholder_in(tcol, r) - ) { - urc[tcol].push(r); - } else { - break; - } - } - - if (!urc[tcol].length) { - result = false; - return true; - } - - }); - - if (!result) { return false; } - - /* get common rows starting from upper position in all the columns - * that widget occupies */ - r = row; - for (r = 1; r < actual_row; r++) { - var common = true; - - for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { - if (urc[uc] && $.inArray(r, urc[uc]) === -1) { - common = false; - } - } - - if (common === true) { - result = r; - break; - } - } - - return result; - }; - - - fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { - var actual_row = widget_grid_data.row; - var diffs = []; - var parent_max_y = parent_bgd.row + parent_bgd.size_y; - - this.for_each_column_occupied(widget_grid_data, function(col) { - var temp_y_units = 0; - - for (var r = parent_max_y; r < actual_row; r++) { - if (this.is_empty(col, r)) { - temp_y_units = temp_y_units + 1; - } - } - - diffs.push(temp_y_units); - }); - - var max_diff = Math.max.apply(Math, diffs); - y_units = (y_units - max_diff); - - return y_units > 0 ? y_units : 0; - }; - - - /** - * Get widgets below a widget. - * - * @method widgets_below - * @param {HTMLElement} $el The jQuery wrapped HTMLElement. - * @return {jQuery} A jQuery collection of HTMLElements. - */ - fn.widgets_below = function($el) { - var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; - var self = this; - var ga = this.gridmap; - var next_row = el_grid_data.row + el_grid_data.size_y - 1; - var $nexts = $([]); - - this.for_each_column_occupied(el_grid_data, function(col) { - self.for_each_widget_below(col, next_row, function(tcol, trow) { - if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { - $nexts = $nexts.add(this); - return true; // break - } - }); - }); - - return Gridster.sort_by_row_asc($nexts); - }; - - - /** - * Update the array of mapped positions with the new player position. - * - * @method set_cells_player_occupies - * @param {Number} col The new player col. - * @param {Number} col The new player row. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.set_cells_player_occupies = function(col, row) { - this.remove_from_gridmap(this.placeholder_grid_data); - this.placeholder_grid_data.col = col; - this.placeholder_grid_data.row = row; - this.add_to_gridmap(this.placeholder_grid_data, this.$player); - return this; - }; - - - /** - * Remove from the array of mapped positions the reference to the player. - * - * @method empty_cells_player_occupies - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.empty_cells_player_occupies = function() { - this.remove_from_gridmap(this.placeholder_grid_data); - return this; - }; - - - fn.can_go_up = function($el) { - var el_grid_data = $el.coords().grid; - var initial_row = el_grid_data.row; - var prev_row = initial_row - 1; - var ga = this.gridmap; - var upper_rows_by_column = []; - - var result = true; - if (initial_row === 1) { return false; } - - this.for_each_column_occupied(el_grid_data, function(col) { - var $w = this.is_widget(col, prev_row); - - if (this.is_occupied(col, prev_row) || - this.is_player(col, prev_row) || - this.is_placeholder_in(col, prev_row) || - this.is_player_in(col, prev_row) - ) { - result = false; - return true; //break - } - }); - - return result; - }; - - - /** - * Check if it's possible to move a widget to a specific col/row. It takes - * into account the dimensions (`size_y` and `size_x` attrs. of the grid - * coords object) the widget occupies. - * - * @method can_move_to - * @param {Object} widget_grid_data The grid coords object that represents - * the widget. - * @param {Object} col The col to check. - * @param {Object} row The row to check. - * @param {Number} [max_row] The max row allowed. - * @return {Boolean} Returns true if all cells are empty, else return false. - */ - fn.can_move_to = function(widget_grid_data, col, row, max_row) { - var ga = this.gridmap; - var $w = widget_grid_data.el; - var future_wd = { - size_y: widget_grid_data.size_y, - size_x: widget_grid_data.size_x, - col: col, - row: row - }; - var result = true; - - //Prevents widgets go out of the grid - var right_col = col + widget_grid_data.size_x - 1; - if (right_col > this.cols) { - return false; - } - - if (max_row && max_row < row + widget_grid_data.size_y - 1) { - return false; - } - - this.for_each_cell_occupied(future_wd, function(tcol, trow) { - var $tw = this.is_widget(tcol, trow); - if ($tw && (!widget_grid_data.el || $tw.is($w))) { - result = false; - } - }); - - return result; - }; - - - /** - * Given the leftmost column returns all columns that are overlapping - * with the player. - * - * @method get_targeted_columns - * @param {Number} [from_col] The leftmost column. - * @return {Array} Returns an array with column numbers. - */ - fn.get_targeted_columns = function(from_col) { - var max = (from_col || this.player_grid_data.col) + - (this.player_grid_data.size_x - 1); - var cols = []; - for (var col = from_col; col <= max; col++) { - cols.push(col); - } - return cols; - }; - - - /** - * Given the upper row returns all rows that are overlapping with the player. - * - * @method get_targeted_rows - * @param {Number} [from_row] The upper row. - * @return {Array} Returns an array with row numbers. - */ - fn.get_targeted_rows = function(from_row) { - var max = (from_row || this.player_grid_data.row) + - (this.player_grid_data.size_y - 1); - var rows = []; - for (var row = from_row; row <= max; row++) { - rows.push(row); - } - return rows; - }; - - /** - * Get all columns and rows that a widget occupies. - * - * @method get_cells_occupied - * @param {Object} el_grid_data The grid coords object of the widget. - * @return {Object} Returns an object like `{ cols: [], rows: []}`. - */ - fn.get_cells_occupied = function(el_grid_data) { - var cells = { cols: [], rows: []}; - var i; - if (arguments[1] instanceof $) { - el_grid_data = arguments[1].coords().grid; - } - - for (i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - cells.cols.push(col); - } - - for (i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - cells.rows.push(row); - } - - return cells; - }; - - - /** - * Iterate over the cells occupied by a widget executing a function for - * each one. - * - * @method for_each_cell_occupied - * @param {Object} el_grid_data The grid coords object that represents the - * widget. - * @param {Function} callback The function to execute on each column - * iteration. Column and row are passed as arguments. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_cell_occupied = function(grid_data, callback) { - this.for_each_column_occupied(grid_data, function(col) { - this.for_each_row_occupied(grid_data, function(row) { - callback.call(this, col, row); - }); - }); - return this; - }; - - - /** - * Iterate over the columns occupied by a widget executing a function for - * each one. - * - * @method for_each_column_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The column number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_column_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_x; i++) { - var col = el_grid_data.col + i; - callback.call(this, col, el_grid_data); - } - }; - - - /** - * Iterate over the rows occupied by a widget executing a function for - * each one. - * - * @method for_each_row_occupied - * @param {Object} el_grid_data The grid coords object that represents - * the widget. - * @param {Function} callback The function to execute on each column - * iteration. The row number is passed as first argument. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_row_occupied = function(el_grid_data, callback) { - for (var i = 0; i < el_grid_data.size_y; i++) { - var row = el_grid_data.row + i; - callback.call(this, row, el_grid_data); - } - }; - - - - fn._traversing_widgets = function(type, direction, col, row, callback) { - var ga = this.gridmap; - if (!ga[col]) { return; } - - var cr, max; - var action = type + '/' + direction; - if (arguments[2] instanceof $) { - var el_grid_data = arguments[2].coords().grid; - col = el_grid_data.col; - row = el_grid_data.row; - callback = arguments[3]; - } - var matched = []; - var trow = row; - - - var methods = { - 'for_each/above': function() { - while (trow--) { - if (trow > 0 && this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } - } - } - }, - 'for_each/below': function() { - for (trow = row + 1, max = ga[col].length; trow < max; trow++) { - if (this.is_widget(col, trow) && - $.inArray(ga[col][trow], matched) === -1 - ) { - cr = callback.call(ga[col][trow], col, trow); - matched.push(ga[col][trow]); - if (cr) { break; } - } - } - } - }; - - if (methods[action]) { - methods[action].call(this); - } - }; - - - /** - * Iterate over each widget above the column and row specified. - * - * @method for_each_widget_above - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery - * wrapped HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_above = function(col, row, callback) { - this._traversing_widgets('for_each', 'above', col, row, callback); - return this; - }; - - - /** - * Iterate over each widget below the column and row specified. - * - * @method for_each_widget_below - * @param {Number} col The column to start iterating. - * @param {Number} row The row to start iterating. - * @param {Function} callback The function to execute on each widget - * iteration. The value of `this` inside the function is the jQuery wrapped - * HTMLElement. - * @return {Class} Returns the instance of the Gridster Class. - */ - fn.for_each_widget_below = function(col, row, callback) { - this._traversing_widgets('for_each', 'below', col, row, callback); - return this; - }; - - - /** - * Returns the highest occupied cell in the grid. - * - * @method get_highest_occupied_cell - * @return {Object} Returns an object with `col` and `row` numbers. - */ - fn.get_highest_occupied_cell = function() { - var r; - var gm = this.gridmap; - var rl = gm[1].length; - var rows = [], cols = []; - var row_in_col = []; - for (var c = gm.length - 1; c >= 1; c--) { - for (r = rl - 1; r >= 1; r--) { - if (this.is_widget(c, r)) { - rows.push(r); - cols.push(c); - break; - } - } - } - - return { - col: Math.max.apply(Math, cols), - row: Math.max.apply(Math, rows) - }; - }; - - - fn.get_widgets_from = function(col, row) { - var ga = this.gridmap; - var $widgets = $(); - - if (col) { - $widgets = $widgets.add( - this.$widgets.filter(function() { - var tcol = $(this).attr('data-col'); - return (tcol === col || tcol > col); - }) - ); - } - - if (row) { - $widgets = $widgets.add( - this.$widgets.filter(function() { - var trow = $(this).attr('data-row'); - return (trow === row || trow > row); - }) - ); - } - - return $widgets; - }; - - - /** - * Set the current height of the parent grid. - * - * @method set_dom_grid_height - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_height = function(height) { - if (typeof height === 'undefined') { - var r = this.get_highest_occupied_cell().row; - height = r * this.min_widget_height; - } - - this.container_height = height; - this.$el.css('height', this.container_height); - return this; - }; - - /** - * Set the current width of the parent grid. - * - * @method set_dom_grid_width - * @return {Object} Returns the instance of the Gridster class. - */ - fn.set_dom_grid_width = function(cols) { - if (typeof cols === 'undefined') { - cols = this.get_highest_occupied_cell().col; - } - - var max_cols = (this.options.autogrow_cols ? this.options.max_cols : - this.cols); - - cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); - this.container_width = cols * this.min_widget_width; - this.$el.css('width', this.container_width); - return this; - }; - - - /** - * It generates the neccessary styles to position the widgets. - * - * @method generate_stylesheet - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_stylesheet = function(opts) { - var styles = ''; - var max_size_x = this.options.max_size_x || this.cols; - var max_rows = 0; - var max_cols = 0; - var i; - var rules; - - opts || (opts = {}); - opts.cols || (opts.cols = this.cols); - opts.rows || (opts.rows = this.rows); - opts.namespace || (opts.namespace = this.options.namespace); - opts.widget_base_dimensions || - (opts.widget_base_dimensions = this.options.widget_base_dimensions); - opts.widget_margins || - (opts.widget_margins = this.options.widget_margins); - opts.min_widget_width = (opts.widget_margins[0] * 2) + - opts.widget_base_dimensions[0]; - opts.min_widget_height = (opts.widget_margins[1] * 2) + - opts.widget_base_dimensions[1]; - - // don't duplicate stylesheets for the same configuration - var serialized_opts = $.param(opts); - if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { - return false; - } - - this.generated_stylesheets.push(serialized_opts); - Gridster.generated_stylesheets.push(serialized_opts); - - /* generate CSS styles for cols */ - for (i = opts.cols; i >= 0; i--) { - styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + - ((i * opts.widget_base_dimensions[0]) + - (i * opts.widget_margins[0]) + - ((i + 1) * opts.widget_margins[0])) + 'px; }\n'); - } - - /* generate CSS styles for rows */ - for (i = opts.rows; i >= 0; i--) { - styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + - ((i * opts.widget_base_dimensions[1]) + - (i * opts.widget_margins[1]) + - ((i + 1) * opts.widget_margins[1]) ) + 'px; }\n'); - } - - for (var y = 1; y <= opts.rows; y++) { - styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + - (y * opts.widget_base_dimensions[1] + - (y - 1) * (opts.widget_margins[1] * 2)) + 'px; }\n'); - } - - for (var x = 1; x <= max_size_x; x++) { - styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + - (x * opts.widget_base_dimensions[0] + - (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n'); - } - - this.remove_style_tags(); - - return this.add_style_tag(styles); - }; - - - /** - * Injects the given CSS as string to the head of the document. - * - * @method add_style_tag - * @param {String} css The styles to apply. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_style_tag = function(css) { - var d = document; - var tag = d.createElement('style'); - - d.getElementsByTagName('head')[0].appendChild(tag); - tag.setAttribute('type', 'text/css'); - - if (tag.styleSheet) { - tag.styleSheet.cssText = css; - } else { - tag.appendChild(document.createTextNode(css)); - } - - this.$style_tags = this.$style_tags.add(tag); - - return this; - }; - - - /** - * Remove the style tag with the associated id from the head of the document - * - * @method remove_style_tag - * @return {Object} Returns the instance of the Gridster class. - */ - fn.remove_style_tags = function() { - var all_styles = Gridster.generated_stylesheets; - var ins_styles = this.generated_stylesheets; - - this.$style_tags.remove(); - - Gridster.generated_stylesheets = $.map(all_styles, function(s) { - if ($.inArray(s, ins_styles) === -1) { return s; } - }); - }; - - - /** - * Generates a faux grid to collide with it when a widget is dragged and - * detect row or column that we want to go. - * - * @method generate_faux_grid - * @param {Number} rows Number of columns. - * @param {Number} cols Number of rows. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_faux_grid = function(rows, cols) { - this.faux_grid = []; - this.gridmap = []; - var col; - var row; - for (col = cols; col > 0; col--) { - this.gridmap[col] = []; - for (row = rows; row > 0; row--) { - this.add_faux_cell(row, col); - } - } - return this; - }; - - - /** - * Add cell to the faux grid. - * - * @method add_faux_cell - * @param {Number} row The row for the new faux cell. - * @param {Number} col The col for the new faux cell. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cell = function(row, col) { - var coords = $({ - left: this.baseX + ((col - 1) * this.min_widget_width), - top: this.baseY + (row -1) * this.min_widget_height, - width: this.min_widget_width, - height: this.min_widget_height, - col: col, - row: row, - original_col: col, - original_row: row - }).coords(); - - if (!$.isArray(this.gridmap[col])) { - this.gridmap[col] = []; - } - - this.gridmap[col][row] = false; - this.faux_grid.push(coords); - - return this; - }; - - - /** - * Add rows to the faux grid. - * - * @method add_faux_rows - * @param {Number} rows The number of rows you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_rows = function(rows) { - var actual_rows = this.rows; - var max_rows = actual_rows + (rows || 1); - - for (var r = max_rows; r > actual_rows; r--) { - for (var c = this.cols; c >= 1; c--) { - this.add_faux_cell(r, c); - } - } - - this.rows = max_rows; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - /** - * Add cols to the faux grid. - * - * @method add_faux_cols - * @param {Number} cols The number of cols you want to add to the faux grid. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.add_faux_cols = function(cols) { - var actual_cols = this.cols; - var max_cols = actual_cols + (cols || 1); - max_cols = Math.min(max_cols, this.options.max_cols); - - for (var c = actual_cols + 1; c <= max_cols; c++) { - for (var r = this.rows; r >= 1; r--) { - this.add_faux_cell(r, c); - } - } - - this.cols = max_cols; - - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this; - }; - - - /** - * Recalculates the offsets for the faux grid. You need to use it when - * the browser is resized. - * - * @method recalculate_faux_grid - * @return {Object} Returns the instance of the Gridster class. - */ - fn.recalculate_faux_grid = function() { - var aw = this.$wrapper.width(); - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; - - $.each(this.faux_grid, $.proxy(function(i, coords) { - this.faux_grid[i] = coords.update({ - left: this.baseX + (coords.data.col -1) * this.min_widget_width, - top: this.baseY + (coords.data.row -1) * this.min_widget_height - }); - }, this)); - - return this; - }; - - - /** - * Get all widgets in the DOM and register them. - * - * @method get_widgets_from_DOM - * @return {Object} Returns the instance of the Gridster class. - */ - fn.get_widgets_from_DOM = function() { - var widgets_coords = this.$widgets.map($.proxy(function(i, widget) { - var $w = $(widget); - return this.dom_to_coords($w); - }, this)); - - widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); - - var changes = $(widgets_coords).map($.proxy(function(i, wgd) { - return this.register_widget(wgd) || null; - }, this)); - - if (changes.length) { - this.$el.trigger('gridster:positionschanged'); - } - - return this; - }; - - - /** - * Calculate columns and rows to be set based on the configuration - * parameters, grid dimensions, etc ... - * - * @method generate_grid_and_stylesheet - * @return {Object} Returns the instance of the Gridster class. - */ - fn.generate_grid_and_stylesheet = function() { - var aw = this.$wrapper.width(); - var max_cols = this.options.max_cols; - - var cols = Math.floor(aw / this.min_widget_width) + - this.options.extra_cols; - - var actual_cols = this.$widgets.map(function() { - return $(this).attr('data-col'); - }).get(); - - //needed to pass tests with phantomjs - actual_cols.length || (actual_cols = [0]); - - var min_cols = Math.max.apply(Math, actual_cols); - - this.cols = Math.max(min_cols, cols, this.options.min_cols); - - if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { - this.cols = max_cols; - } - - // get all rows that could be occupied by the current widgets - var max_rows = this.options.extra_rows; - this.$widgets.each(function(i, w) { - max_rows += (+$(w).attr('data-sizey')); - }); - - this.rows = Math.max(max_rows, this.options.min_rows); - - this.baseX = ($(window).width() - aw) / 2; - this.baseY = this.$wrapper.offset().top; + }; + + + /** + * checks the grid to see if the desired column is a valid row in the config + * @Param {Number} col number to check + * @Param {Number} [size_y] optional number of columns in the offset + * @Return {Boolean} true if the desire column exists in the grid. + */ + fn.is_valid_col = function (col, size_x) { + //if the grid is set to autogrow all cols are valid + if (this.options.max_cols === Infinity) { + return true; + } + return this.cols >= this.calculate_highest_col(col, size_x) ; + }; + + /** + * checks the grid to see if the desired row is a valid row in the config + * @Param {Number} row number to check + * @Param {Number} [size_y] optional number of rows in the offset + * @Return {Boolean} true if the desire row exists in the grid. + */ + fn.is_valid_row = function (row, size_y){ + return this.rows >= this.calculate_highest_row(row, size_y); + }; + + /** + * extract out the logic to calculate the highest col the widget needs + * in the grid in order to fit. Based on the current row and desired size + * @param {Number} col the column number of the current postiton of the widget + * @param {Number} [size_x] veritical size of the widget + * @returns {number} highest col needed to contain the widget + */ + fn.calculate_highest_col = function (col, size_x) { + return col + (size_x || 1) - 1; + }; + + /** + * extract out the logic to calculate the highest row the widget needs + * in the grid in order to fit. Based on the current row and desired size + * @param {Number} row the row number of the current postiton of the widget + * @param {Number} [size_y] horizontal size of the widget + * @returns {number} highest row needed to contain the widget + */ + fn.calculate_highest_row = function (row, size_y){ + return row + (size_y || 1) - 1; + }; + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function (col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function (col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + /** + * Determines if widget is supposed to be static. + * WARNING: as of 0.6.6 this feature is buggy when + * used with resizable widgets, as resizing widgets + * above and below a static widgit can cause it to move. + * This feature is considered experimental at this time + * @method is_static + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true if widget exists and has static class, + * else returns false + */ + fn.is_static = function (col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + if (cell.hasClass(this.options.static_class)) { + return true; + } + } + + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function (col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + + /** + * Get widgets overlapping with the player or with the object passed + * representing the grid cells. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function (cells) { + cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []}); + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function (i, col) { + $.each(cells.rows, $.proxy(function (i, row) { + if (this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function (col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + + // Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); + } + + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; + + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.$preview_holder.attr({ + 'data-row': row, + 'data-col': col + }); + + if (this.options.shift_player_up) { + if (moved_down || changed_column) { + + var $nexts = this.widgets_below({ + col: phgd.col, + row: phgd.row, + size_y: phgd.size_y, + size_x: phgd.size_x + }); + + $nexts.each($.proxy(function (i, widget) { + //Make sure widget is at it's topmost position + var $w = $(widget); + var wgd = $w.coords().grid; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + this.move_widget_to($w, can_go_widget_up); + } + + }, this)); + } + + var $widgets_under_ph = this.get_widgets_under_player( + this.cells_occupied_by_placeholder); + + if ($widgets_under_ph.length) { + $widgets_under_ph.each($.proxy(function (i, widget) { + var $w = $(widget); + this.move_widget_down( + $w, row + phgd.size_y - $w.data('coords').grid.row); + }, this)); + } + } + + }; + + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function (widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || + this.is_widget(tcol, r) && + grid_col[r].is($widgets_under_player) + ) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + } else { + break; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function (a, b) { + return a - b; + }); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function (widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + /* generate an array with columns as index and array with topmost rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + // iterate over each row + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + } + } + + if (!this.is_player(tcol, r) && !this.is_placeholder_in(tcol, r) && !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + } + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(function (a, b) { + return a - b; + }); + }); + + if (!result) { + return false; + } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function (widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row) { + var common = true; + /*jshint -W083 */ + $.each(upper_rows, function (col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + /*jshint +W083 */ + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + } else { + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index( + valid_rows, size_y); + } + } + + return new_row; + }; + + + fn.get_consecutive_numbers_index = function (arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; // or null? + + for (var i = 0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + } else { + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function () { + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function (i, col) { + $.each(rows_from_bottom, $.proxy(function (i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { + return true; + } //next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && + $.inArray($w, used) === -1 + ) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function (col) { + this.set_player(col, undefined , false); + }; + + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function (row) { + this.set_player(undefined, row, false); + }; + + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function (col) { + //this.set_player(col, false); + var self = this; + if (this.options.shift_larger_widgets_down) { + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], + function (tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {jQuery} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function (row) { + //this.set_player(false, row); + var self = this; + var cols = this.cells_occupied_by_player.cols; + if (this.options.shift_larger_widgets_down) { + /*jshint -W083 */ + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function (tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + /*jshint +W083 */ + } + }; + + //Not yet part of api - DM. + fn.new_move_widget_to = function ($widget, col, row) { + var widget_grid_data = $widget.coords().grid; + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + widget_grid_data.col = col; + + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + $widget.attr('data-col', col); + this.update_widget_position(widget_grid_data, $widget); + this.$changed = this.$changed.add($widget); + + return this; + }; + + + /** + * Move a widget to a specific row and column. + * If the widget has widgets below, all of these widgets will be moved also + * + * @method move_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @param {Number} new_col the column number to be set in widget + * @param {Number} new_row the row number to be set in widget + * @param {Function} callback is called when whole process is done. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.move_widget = function ($widget, new_col, new_row, callback) { + var wgd = $widget.coords().grid; + + var new_grid_data = { + col: new_col, + row: new_row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + + this.mutate_widget_in_gridmap($widget, wgd, new_grid_data); + + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + if (callback) { + callback.call(this, new_grid_data.col, new_grid_data.row); + } + + return $widget; + }; + + + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Gridster} Returns the instance of the Gridster Class. + * @param row - row to move the widget to + */ + fn.move_widget_to = function ($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to( + widget_grid_data, widget_grid_data.col, row); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + + $next_widgets.each(function (i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Boolean} Returns if the widget moved + */ + fn.move_widget_up = function ($widget, y_units) { + if (y_units === undefined){ + return false; + } + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + y_units || (y_units = 1); + + if (!this.can_go_up($widget)) { + return false; + } //break; + + this.for_each_column_occupied(el_grid_data, function (col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row( + widget_grid_data, col, next_row); + + if (!next_row) { + return true; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + /* $next_widgets.each($.proxy(function(i, widget) { + console.log('from_within_move_widget_up'); + this.move_widget_up($(widget), y_units); + }, this)); */ + } + }); + + }; + + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {jQuery} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} y_units The number of cells that the widget has to move. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function ($widget, y_units) { + var el_grid_data, actual_row, moved, y_diff; + + if (y_units <= 0) { + return false; + } + + el_grid_data = $widget.coords().grid; + actual_row = el_grid_data.row; + moved = []; + y_diff = y_units; + + if (!$widget) { + return false; + } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function (i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff( + wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + + widget_grid_data.row = next_row; + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + } + }; + + + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function (widget_grid_data, col, row) { + var result = true; + var urc = []; // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function (tcol) { + urc[tcol] = []; + + r = actual_row; + while (r--) { + if (this.is_empty(tcol, r) && !this.is_placeholder_in(tcol, r) + ) { + urc[tcol].push(r); + } else { + break; + } + } + + if (!urc[tcol].length) { + result = false; + return true; + } + + }); + + if (!result) { + return false; + } + + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for (r = 1; r < actual_row; r++) { + var common = true; + + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } + + if (common === true) { + result = r; + break; + } + } + + return result; + }; + + + fn.displacement_diff = function (widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; + + this.for_each_column_occupied(widget_grid_data, function (col) { + var temp_y_units = 0; + + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; + } + } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {object} $el The jQuery wrapped HTMLElement. + * @return {jQuery} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function ($el) { + var $nexts = $([]); + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + if (el_grid_data === undefined) { + //there is no grid, so we can't calculate the widgets below + return $nexts; + } + var self = this; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + + this.for_each_column_occupied(el_grid_data, function (col) { + self.for_each_widget_below(col, next_row, function (tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; // break + } + }); + }); + + return Gridster.sort_by_row_asc($nexts); + }; + + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Gridster} Returns the instance of the Gridster Class. + * @param row + */ + fn.set_cells_player_occupies = function (col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function () { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + fn.can_go_down = function ($el) { + var can_go_down = true; + var $gr = this; + + if ($el.hasClass(this.options.static_class)) { + can_go_down = false; + } + + this.widgets_below($el).each(function () { + if ($(this).hasClass($gr.options.static_class)) { + can_go_down = false; + } + }); + + return can_go_down; + }; + + + fn.can_go_up = function ($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + + var result = true; + if (initial_row === 1) { + return false; + } + + this.for_each_column_occupied(el_grid_data, function (col) { + if (this.is_occupied(col, prev_row) || + this.is_player(col, prev_row) || + this.is_placeholder_in(col, prev_row) || + this.is_player_in(col, prev_row) + ) { + result = false; + return true; //break + } + }); + + return result; + }; + + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function (widget_grid_data, col, row) { + var $w = widget_grid_data.el; + var future_wd = { + size_y: widget_grid_data.size_y, + size_x: widget_grid_data.size_x, + col: col, + row: row + }; + var result = true; + + //Prevents widgets go out of the grid, check only if the grid is not set to autogrow + if (this.options.max_cols !== Infinity) { + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + } + } + + if (this.options.max_rows < row + widget_grid_data.size_y - 1) { + return false; + } + + this.for_each_cell_occupied(future_wd, function (tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function (from_col) { + var max = (from_col || this.player_grid_data.col) + + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function (from_row) { + var max = (from_row || this.player_grid_data.row) + + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function (el_grid_data) { + var cells = {cols: [], rows: []}; + var i; + if (arguments[1] instanceof $) { + el_grid_data = arguments[1].coords().grid; + } + + for (i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for (i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * widget. + * @param grid_data + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function (grid_data, callback) { + this.for_each_column_occupied(grid_data, function (col) { + this.for_each_row_occupied(grid_data, function (row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function (el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function (el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + fn.clean_up_changed = function () { + var $gr = this; + $gr.$changed.each(function () { + if ($gr.options.shift_larger_widgets_down) { + $gr.move_widget_up($(this)); + } + }); + }; + + + /** + * Traverse a series of widgets + * @param type - currently supported 'for_each' + * @param direction - The direction to traverse. Supports 'above' and 'below' + * NOTE: the directions are based on the layout in the grid above is toward the top + * and below is toward the bottom. EG opposite direction of the row numbers + * @param col - column to traverse + * @param row - starting row in the column + * @param callback - a function that will be called for every widget found + * @private + */ + fn._traversing_widgets = function (type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { + return; + } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof $) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + + var methods = { + 'for_each/above': function () { + while (trow--) { + if (trow > 0 && this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { + break; + } + } + } + }, + 'for_each/below': function () { + for (trow = row + 1, max = ga[col].length; trow < max; trow++) { + if (this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + //break was causing problems, leaving for testing. + //if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function (col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Gridster} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function (col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function () { + var r; + var gm = this.gridmap; + var rl = gm[1].length; + var rows = [], cols = []; + for (var c = gm.length - 1; c >= 1; c--) { + for (r = rl - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + cols.push(c); + break; + } + } + } + + return { + col: Math.max.apply(Math, cols), + row: Math.max.apply(Math, rows) + }; + }; + + /** + * return the widgets what exist within the given range of grid cells + * @param col1 - col of upper left search + * @param row1 - row of upper left search + * @param col2 - col of lower right search + * @param row2 - row of lower right search + * @returns {*|jQuery|HTMLElement} - a collection of the cells within the range + */ + fn.get_widgets_in_range = function (col1, row1, col2, row2) { + var $widgets = $([]); + var c, r, $w, wgd; + + for (c = col2; c >= col1; c--) { + for (r = row2; r >= row1; r--) { + $w = this.is_widget(c, r); + + if ($w !== false) { + wgd = $w.data('coords').grid; + if (wgd.col >= col1 && wgd.col <= col2 && + wgd.row >= row1 && wgd.row <= row2 + ) { + $widgets = $widgets.add($w); + } + } + } + } + + return $widgets; + }; + + /** + * return any widget which is located at the given coordinates + * @param col - col to search at + * @param row - row to search at + * @returns {*} - a collection of any widgets found. + */ + fn.get_widgets_at_cell = function (col, row) { + return this.get_widgets_in_range(col, row, col, row); + }; + + /** + * gets the list of widgets in either the row or the col passed in_loop + * Not sure if this makes sense for the API or what the use case is, + * but I'm documenting it as it exists. + * @param col - a col to search for widgets from + * @param row - a row to search for widgets from + * @returns {*|jQuery|HTMLElement} - a collection of the widgets in either + * the row or the col passed in. + * + * @deprecated - if you want to search for widgets in a cell or a range + * look at get_widgets_in_range and get_widgets_at_cell + */ + fn.get_widgets_from = function (col, row) { + var $widgets = $(); + + if (col) { + $widgets = $widgets.add( + this.$widgets.filter(function () { + var tcol = parseInt($(this).attr('data-col')); + return (tcol === col || tcol > col); + }) + ); + } + + if (row) { + $widgets = $widgets.add( + this.$widgets.filter(function () { + var trow = parseInt($(this).attr('data-row')); + return (trow === row || trow > row); + }) + ); + } + + return $widgets; + }; + + + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function (height) { + if (typeof height === 'undefined') { + var r = this.get_highest_occupied_cell().row; + height = ((r + 1) * this.options.widget_margins[1]) + (r * this.min_widget_height); + } + + this.container_height = height; + this.$el.css('height', this.container_height); + return this; + }; + + /** + * Set the current width of the parent grid. + * + * @method set_dom_grid_width + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_width = function (cols) { + if (typeof cols === 'undefined') { + cols = this.get_highest_occupied_cell().col; + } + + var max_cols = (this.options.max_cols === Infinity ? this.options.max_cols : this.cols); + + cols = Math.min(max_cols, Math.max(cols, this.options.min_cols)); + this.container_width = ((cols + 1) * this.options.widget_margins[0]) + (cols * this.min_widget_width); + if (this.is_responsive()) { + this.$el.css({'min-width': '100%', 'max-width': '100%'}); + return this; //if we are responsive exit before setting the width of $el + } + this.$el.css('width', this.container_width); + + return this; + }; + + + /** + * Checks if this grid is responsive. + * autogenerate_stylesheet be true, the widget base width should be auto, and there must be a max_cols set. + * @returns {Boolean} + */ + fn.is_responsive = function () { + return this.options.autogenerate_stylesheet && this.options.widget_base_dimensions[0] === 'auto' && this.options.max_cols !== Infinity; + }; + + /** + * Generates the width of the grid columns based on the width of the window. + * @returns {number} + */ + fn.get_responsive_col_width = function () { + var cols = this.cols || this.options.max_cols; + return (this.$el[0].clientWidth - 3 - ((cols + 1) * this.options.widget_margins[0])) / cols; + }; + + /** + * Changes the minimum width of a widget based on the width of the window and the number of cols that can + * fit in it. + * @returns {Gridster} + */ + fn.resize_responsive_layout = function () { + this.min_widget_width = this.get_responsive_col_width(); + this.generate_stylesheet(); + this.update_widgets_dimensions(); + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + return this; + }; + + /** + * Switches between collapsed widgets the span the full width when the responsive_breakpoint is triggered. + * @param collapse + * @param opts + * @returns {Gridster} + */ + fn.toggle_collapsed_grid = function (collapse, opts) { + if (collapse) { + this.$widgets.css({ + 'margin-top': opts.widget_margins[0], + 'margin-bottom': opts.widget_margins[0], + 'min-height': opts.widget_base_dimensions[1] + }); + + this.$el.addClass('collapsed'); + + if (this.resize_api) { + this.disable_resize(); + } + + if (this.drag_api) { + this.disable(); + } + } else { + this.$widgets.css({ + 'margin-top': 'auto', + 'margin-bottom': 'auto', + 'min-height': 'auto' + }); + this.$el.removeClass('collapsed'); + if (this.resize_api) { + this.enable_resize(); + } + + if (this.drag_api) { + this.enable(); + } + } + return this; + }; + + /** + * It generates the necessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {object} [opts] - set of gridster config options to generate stylessheets based on + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function (opts) { + var styles = ''; + var i; + var full_width = this.is_responsive() && this.options.responsive_breakpoint && ($(window).width() < this.options.responsive_breakpoint); + + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = this.options.namespace); + opts.widget_base_dimensions || + (opts.widget_base_dimensions = this.options.widget_base_dimensions); + + opts.widget_margins || (opts.widget_margins = this.options.widget_margins); + + if (this.is_responsive()) { + opts.widget_base_dimensions = [this.get_responsive_col_width(), opts.widget_base_dimensions[1]]; + this.toggle_collapsed_grid(full_width, opts); + } + + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + } + + this.generated_stylesheets.push(serialized_opts); + Gridster.generated_stylesheets.push(serialized_opts); + + /* generate CSS styles for cols */ + for (i = 1; i <= opts.cols + 1; i++) { + styles += (opts.namespace + ' [data-col="' + i + '"] { left:' + + (full_width ? this.options.widget_margins[0] : + ((i * opts.widget_margins[0]) + ((i - 1) * opts.widget_base_dimensions[0]))) + 'px; }\n'); + } + + /* generate CSS styles for rows */ + for (i = 1; i <= opts.rows + 1; i++) { + styles += (opts.namespace + ' [data-row="' + i + '"] { top:' + + ((i * opts.widget_margins[1]) + ((i - 1) * opts.widget_base_dimensions[1])) + 'px; }\n'); + } + + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + + (full_width ? 'auto' : ((y * opts.widget_base_dimensions[1]) + ((y - 1) * opts.widget_margins[1]))) + (full_width ? '' : 'px') + '; }\n'); + + } + + for (var x = 1; x <= opts.cols; x++) { + var colWidth = ((x * opts.widget_base_dimensions[0]) + ((x - 1) * opts.widget_margins[0])); + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + + (full_width ? (this.$wrapper.width() - this.options.widget_margins[0] * 2) : colWidth > this.$wrapper.width() ? this.$wrapper.width() : colWidth ) + 'px; }\n'); + + } + + this.remove_style_tags(); + + return this.add_style_tag(styles); + }; + + + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function (css) { + var d = document; + var cssID = 'gridster-stylesheet'; + if (this.options.namespace !== '') { + cssID = cssID + '-' + this.options.namespace; + } + + if (!document.getElementById(cssID)) { + var tag = d.createElement('style'); + tag.id = cssID; + + d.getElementsByTagName('head')[0].appendChild(tag); + tag.setAttribute('type', 'text/css'); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + } else { + tag.appendChild(document.createTextNode(css)); + } + + this.remove_style_tags(); + this.$style_tags = this.$style_tags.add(tag); + } + + return this; + }; + + + /** + * Remove the style tag with the associated id from the head of the document + * + * @method remove_style_tag + * @return {Object} Returns the instance of the Gridster class. + */ + fn.remove_style_tags = function () { + var all_styles = Gridster.generated_stylesheets; + var ins_styles = this.generated_stylesheets; + + this.$style_tags.remove(); + + Gridster.generated_stylesheets = $.map(all_styles, function (s) { + if ($.inArray(s, ins_styles) === -1) { + return s; + } + }); + }; + + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function (rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for (col = cols; col > 0; col--) { + this.gridmap[col] = []; + for (row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function (row, col) { + var coords = $({ + left: this.baseX + ((col - 1) * this.min_widget_width), + top: this.baseY + (row - 1) * this.min_widget_height, + width: this.min_widget_width, + height: this.min_widget_height, + col: col, + row: row, + original_col: col, + original_row: row + }).coords(); + + if (!$.isArray(this.gridmap[col])) { + this.gridmap[col] = []; + } + + if (typeof this.gridmap[col][row] === 'undefined') { + this.gridmap[col][row] = false; + } + this.faux_grid.push(coords); + + return this; + }; + + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function (rows) { + rows = window.parseInt(rows, 10); + + var actual_rows = this.rows; + var max_rows = actual_rows + parseInt(rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + } + } + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + /** + * Add cols to the faux grid. + * + * @method add_faux_cols + * @param {Number} cols The number of cols you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cols = function (cols) { + cols = window.parseInt(cols, 10); + + var actual_cols = this.cols; + var max_cols = actual_cols + parseInt(cols || 1); + max_cols = Math.min(max_cols, this.options.max_cols); + + for (var c = actual_cols + 1; c <= max_cols; c++) { + for (var r = this.rows; r >= 1; r--) { + this.add_faux_cell(r, c); + } + } + + this.cols = max_cols; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function () { + var aw = this.$wrapper.width(); + this.baseX = ($window.width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.$wrapper.css('position') === 'relative') { + this.baseX = this.baseY = 0; + } + + $.each(this.faux_grid, $.proxy(function (i, coords) { + this.faux_grid[i] = coords.update({ + left: this.baseX + (coords.data.col - 1) * this.min_widget_width, + top: this.baseY + (coords.data.row - 1) * this.min_widget_height + }); + }, this)); + + if (this.is_responsive()) { + this.resize_responsive_layout(); + } + + if (this.options.center_widgets) { + this.center_widgets(); + } + + return this; + }; + + + /** + * Resize dimensions of widgets in grid based on given options + * + * @method resize_widget_dimensions + * @param options + * @returns {Gridster} + */ + fn.resize_widget_dimensions = function (options) { + if (options.widget_margins) { + this.options.widget_margins = options.widget_margins; + } + + if (options.widget_base_dimensions) { + this.options.widget_base_dimensions = options.widget_base_dimensions; + } + + this.$widgets.each($.proxy(function (i, widget) { + var $widget = $(widget); + this.resize_widget($widget); + }, this)); + + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.set_dom_grid_width(); + + return this; + }; + + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function () { + var widgets_coords = this.$widgets.map($.proxy(function (i, widget) { + var $w = $(widget); + return this.dom_to_coords($w); + }, this)); + + widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords); + + var changes = $(widgets_coords).map($.proxy(function (i, wgd) { + return this.register_widget(wgd) || null; + }, this)); + + if (changes.length) { + this.$el.trigger('gridster:positionschanged'); + } + + return this; + }; + + fn.get_num_widgets = function () { + return this.$widgets.size(); + }; + + /** + * Helper function used to set the current number of columns in the grid + * + * @param wrapper_width + */ + fn.set_num_columns = function (wrapper_width) { + + var max_cols = this.options.max_cols; + + var cols = Math.floor(wrapper_width / (this.min_widget_width + this.options.widget_margins[0])) + + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function () { + return $(this).attr('data-col'); + }).get(); + + //needed to pass tests with phantomjs + actual_cols.length || (actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + this.cols = Math.max(min_cols, cols, this.options.min_cols); + + if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) { + this.cols = max_cols; + } + + if (this.drag_api) { + this.drag_api.set_limits((this.cols * this.min_widget_width) + ((this.cols + 1) * this.options.widget_margins[0])); + } + }; + + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function () { + var aw = this.$wrapper.width(); + + this.set_num_columns(aw); + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function (i, w) { + max_rows += (+$(w).attr('data-sizey')); + }); + + //this.rows = Math.max(max_rows, this.options.min_rows); + this.rows = this.options.max_rows; + + this.baseX = ($window.width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this.generate_faux_grid(this.rows, this.cols); + }; + + /** + * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks + * + * @method destroy + * @param {Boolean} remove If true, remove gridster from DOM. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.destroy = function (remove) { + this.$el.removeData('gridster'); + + // remove coords from elements + $.each(this.$widgets, function () { + $(this).removeData('coords'); + }); - if (this.options.autogenerate_stylesheet) { - this.generate_stylesheet(); - } - - return this.generate_faux_grid(this.rows, this.cols); - }; - - /** - * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks - * - * @method destroy - * @param {Boolean} remove If true, remove gridster from DOM. - * @return {Object} Returns the instance of the Gridster class. - */ - fn.destroy = function(remove) { - this.$el.removeData('gridster'); - - // remove bound callback on window resize - $(window).unbind('.gridster'); + // remove bound callback on window resize + $window.unbind('.gridster'); - if (this.drag_api) { - this.drag_api.destroy(); - } + if (this.drag_api) { + this.drag_api.destroy(); + } + if (this.resize_api) { + this.resize_api.destroy(); + } - this.remove_style_tags(); + this.$widgets.each(function (i, el) { + $(el).coords().destroy(); + }); - remove && this.$el.remove(); + if (this.resize_api) { + this.resize_api.destroy(); + } + + this.remove_style_tags(); - return this; - }; + remove && this.$el.remove(); + + return this; + }; - //jQuery adapter - $.fn.gridster = function(options) { - return this.each(function() { - if (! $(this).data('gridster')) { - $(this).data('gridster', new Gridster( this, options )); - } - }); - }; + //jQuery adapter + $.fn.gridster = function (options) { + return this.each(function () { + var $this = $(this); + if (!$this.data('gridster')) { + $this.data('gridster', new Gridster(this, options)); + } + }); + }; - return Gridster; + return Gridster; })); diff --git a/lib/gridster/src/jquery.gridster.less b/lib/gridster/src/jquery.gridster.less new file mode 100644 index 000000000..ffb40c6d2 --- /dev/null +++ b/lib/gridster/src/jquery.gridster.less @@ -0,0 +1,108 @@ +/*! gridster.js - v0.6.10 - 2015-05-31 +* https://dsmorse.github.io/gridster.js/ +* Copyright (c) 2015 ducksboard; Licensed MIT */ + +.gridster { + position:relative; + & > * { + -webkit-transition: height .4s, width .4s; + -moz-transition: height .4s, width .4s; + -o-transition: height .4s, width .4s; + -ms-transition: height .4s, width .4s; + transition: height .4s, width .4s; + } + .gs-w { + z-index: 2; + position: absolute; + } + .preview-holder { + z-index: 1; + position: absolute; + background-color: #fff; + border-color: #fff; + opacity: 0.3; + } + .player-revert { + z-index: 10!important; + -webkit-transition: left .3s, top .3s!important; + -moz-transition: left .3s, top .3s!important; + -o-transition: left .3s, top .3s!important; + transition: left .3s, top .3s!important; + } +} +.gridster.collapsed { + height: auto !important; + .gs-w { + position: static !important; + } +} +.ready { + .gs-w:not(.preview-holder) { + -webkit-transition: opacity .3s, left .3s, top .3s; + -moz-transition: opacity .3s, left .3s, top .3s; + -o-transition: opacity .3s, left .3s, top .3s; + transition: opacity .3s, left .3s, top .3s; + } +} +.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder { + -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; +} +.gridster .dragging,.gridster .resizing { + z-index: 10!important; + -webkit-transition: all 0s !important; + -moz-transition: all 0s !important; + -o-transition: all 0s !important; + transition: all 0s !important; +} +.gs-resize-handle { + position: absolute; + z-index: 1; +} +.gs-resize-handle-both { + width: 20px; + height: 20px; + bottom: -8px; + right: -8px; + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4='); + background-position: top left; + background-repeat: no-repeat; + cursor: se-resize; + z-index: 20; +} +.gs-resize-handle-x { + top: 0; + bottom: 13px; + right: -5px; + width: 10px; + cursor: e-resize; +} +.gs-resize-handle-y { + left: 0; + right: 13px; + bottom: -5px; + height: 10px; + cursor: s-resize; +} +.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle { + opacity: 1; +} +.gs-resize-handle,.gs-w.dragging .gs-resize-handle { + opacity: 0; +} +.gs-resize-disabled { + .gs-resize-handle { + display: none!important; + } +} +[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle { + display: none !important; +} + +/* Uncomment this if you set helper : "clone" in draggable options */ +/*.gridster .player { + opacity:0; +} +*/ \ No newline at end of file diff --git a/lib/gridster/src/utils.js b/lib/gridster/src/utils.js index f66910d78..5bb0dcbbb 100644 --- a/lib/gridster/src/utils.js +++ b/lib/gridster/src/utils.js @@ -1,72 +1,85 @@ -;(function(window, undefined) { +(function (window, undefined) { + 'use strict'; + /* Delay, debounce and throttle functions taken from underscore.js + * + * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and + * Investigative Reporters & Editors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * 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. + */ - /* Delay, debounce and throttle functions taken from underscore.js - * - * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and - * Investigative Reporters & Editors - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * 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. - */ + window.delay = function (func, wait) { + var args = Array.prototype.slice.call(arguments, 2); + return setTimeout(function () { + return func.apply(null, args); + }, wait); + }; - window.delay = function(func, wait) { - var args = Array.prototype.slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); - }; + window.debounce = function (func, wait, immediate) { + var timeout; + return function () { + var context = this, args = arguments; + var later = function () { + timeout = null; + if (!immediate) { + func.apply(context, args); + } + }; + if (immediate && !timeout) { + func.apply(context, args); + } + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; - window.debounce = function(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - }; - if (immediate && !timeout) func.apply(context, args); - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }; - - window.throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = debounce( - function(){ more = throttling = false; }, wait); - return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) func.apply(context, args); - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - result = func.apply(context, args); - } - whenDone(); - throttling = true; - return result; - }; - }; + window.throttle = function (func, wait) { + var context, args, timeout, throttling, more, result; + var whenDone = debounce( + function () { + more = throttling = false; + }, wait); + return function () { + context = this; + args = arguments; + var later = function () { + timeout = null; + if (more) { + func.apply(context, args); + } + whenDone(); + }; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (throttling) { + more = true; + } else { + result = func.apply(context, args); + } + whenDone(); + throttling = true; + return result; + }; + }; })(window); diff --git a/lib/gridster/test/amd-main.js b/lib/gridster/test/amd-main.js new file mode 100644 index 000000000..3fc75213f --- /dev/null +++ b/lib/gridster/test/amd-main.js @@ -0,0 +1,53 @@ +//Based on https://github.com/jonnyreeves/qunit-require +/* global require, QUnit*/ +'use strict'; +require.config({ + //set the baseUrl to the src dir so that gridster + //AMD modules can be found. + baseUrl: '../src/', + paths: { + 'QUnit': '../libs/qunit/qunit/qunit', + 'jquery': '../libs/jquery/dist/jquery', + 'gridster': 'jquery.gridster' + }, + map: { + // '*' means all modules will get 'jquery-private' + // for their 'jquery' dependency. + '*': { 'jquery': '../test/jquery-private' }, + + // 'jquery-private' wants the real jQuery module + // though. If this line was not here, there would + // be an unresolvable cyclic dependency. + '../test/jquery-private': { 'jquery': 'jquery' } + }, + shim: { + 'QUnit': { + exports: 'QUnit', + init: function() { + QUnit.config.autoload = false; + QUnit.config.autostart = false; + } + } + } +}); +/* + Load all of our require'd files + + We have to load all of the gridster jquery.* modules so + that they are defined for when gridster needs them. + + Lastly, load the testsuite which defines some tests. +*/ +require([ + 'QUnit', + 'utils', + 'jquery.coords', + 'jquery.collision', + 'jquery.draggable', + '../test/testsuite' + //Require'd files are passed as args, but we don't use them. +], function(QUnit/*, utils, coords, collision, draggable, testsuite*/) { + QUnit.load(); + QUnit.start(); + } +); diff --git a/lib/gridster/test/jquery-private.js b/lib/gridster/test/jquery-private.js new file mode 100644 index 000000000..1bc47f4d3 --- /dev/null +++ b/lib/gridster/test/jquery-private.js @@ -0,0 +1,4 @@ +define(['jquery'], function (jq) { + 'use strict'; + return jq.noConflict( true ); +}); diff --git a/lib/gridster/test/jquery.gridder_test.js b/lib/gridster/test/jquery.gridder_test.js deleted file mode 100644 index 2df25e775..000000000 --- a/lib/gridster/test/jquery.gridder_test.js +++ /dev/null @@ -1,38 +0,0 @@ -/*global QUnit:false, module:false, test:false, asyncTest:false, expect:false*/ -/*global start:false, stop:false ok:false, equal:false, notEqual:false, deepEqual:false*/ -/*global notDeepEqual:false, strictEqual:false, notStrictEqual:false, raises:false*/ -(function($) { - - /* - ======== A Handy Little QUnit Reference ======== - http://docs.jquery.com/QUnit - - Test methods: - expect(numAssertions) - stop(increment) - start(decrement) - Test assertions: - ok(value, [message]) - equal(actual, expected, [message]) - notEqual(actual, expected, [message]) - deepEqual(actual, expected, [message]) - notDeepEqual(actual, expected, [message]) - strictEqual(actual, expected, [message]) - notStrictEqual(actual, expected, [message]) - raises(block, [expected], [message]) - */ - - module('jQuery#gridster', { - setup: function() { - - this.el = $('#qunit-fixture').find(".wrapper ul"); - - } - }); - - // test('is chainable', 1, function() { - // // Not a bad test to run on collection methods. - // strictEqual(this.el, this.el.gridster(), 'should be chaninable'); - // }); - -}(jQuery)); diff --git a/lib/gridster/test/jquery.gridder.html b/lib/gridster/test/jquery.gridster-amd.html similarity index 68% rename from lib/gridster/test/jquery.gridder.html rename to lib/gridster/test/jquery.gridster-amd.html index 1750f0a4d..6a6058798 100644 --- a/lib/gridster/test/jquery.gridder.html +++ b/lib/gridster/test/jquery.gridster-amd.html @@ -2,30 +2,17 @@ - gridster.js Test Suite - - - - - - - - + gridster.js AMD Test Suite - - - - - - - - + + + -

    gridster.js Test Suite

    +

    gridster.js AMD Test Suite

    diff --git a/lib/gridster/test/jquery.gridster.html b/lib/gridster/test/jquery.gridster.html new file mode 100644 index 000000000..0cf9d165f --- /dev/null +++ b/lib/gridster/test/jquery.gridster.html @@ -0,0 +1,46 @@ + + + + + gridster.js Test Suite + + + + + + + + + + + + + + + + + + + + + +

    gridster.js Test Suite

    + +

    + +
    +

    +
      +
      + +
      +
        +
      • +
      • +
      +
      +
      +
      + + + diff --git a/lib/gridster/test/jquery.gridster_test.js b/lib/gridster/test/jquery.gridster_test.js new file mode 100644 index 000000000..91439b32f --- /dev/null +++ b/lib/gridster/test/jquery.gridster_test.js @@ -0,0 +1,395 @@ +//jshint quotmark:false +/*global module:false, test:false */ +/*global ok:false, equal:false, notEqual:false, deepEqual:false*/ +/*global notDeepEqual:false */ +(function ($) { +'use strict'; + /* + ======== A Handy Little QUnit Reference ======== + http://docs.jquery.com/QUnit + + Test methods: + expect(numAssertions) + stop(increment) + start(decrement) + Test assertions: + ok(value, [message]) + equal(actual, expected, [message]) + notEqual(actual, expected, [message]) + deepEqual(actual, expected, [message]) + notDeepEqual(actual, expected, [message]) + strictEqual(actual, expected, [message]) + notStrictEqual(actual, expected, [message]) + raises(block, [expected], [message]) + */ + + module('jQuery#gridster', { + setup: function () { + + this.el = $('#qunit-fixture').find(".wrapper ul"); + + this.serialization = [ + {name: "A", col: "1", row: "1", size_x: "2", size_y: "2"}, + {name: "B", col: "4", row: "1", size_x: "1", size_y: "2"}, + {name: "C", col: "10", row: "10", size_x: "10", size_y: "10"}, + {name: "D", col: "3", row: "1", size_x: "1", size_y: "1"}, + {name: "E", col: "2", row: "3", size_x: "3", size_y: "1"} + ]; + + this.serialization_small = [ + {col: 1, row: 1, size_x: 2, size_y: 2}, + {col: 3, row: 1, size_x: 1, size_y: 2}, + {col: 6, row: 1, size_x: 1, size_y: 1} + ]; + } + }); + + test('can count and clear widgets', 2, function () { + var grid = this.el.gridster().data('gridster'); + equal(grid.get_num_widgets(), 2, 'Count the default widgets from the HTML config'); + grid.remove_all_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + }); + + test('Above and below', 12, function () { + var grid = this.el.gridster({ max_cols: 4, max_rows: 4, widget_base_dimensions: [100, 55]}).data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + $.each(this.serialization, function () { + grid.add_widget('
    1. ', this.size_x, this.size_y, this.col, this.row); + }); + equal(grid.get_num_widgets(), this.serialization.length, 'Loaded the widgets for the test'); + var widgets_above = $([]); + //the call here checks above as the user see's it on the screen which will check row below 4 + grid.for_each_widget_above(3, 4, function(tcol, trow) { + widgets_above = widgets_above.add(this); + }); + //widgets B (3,1) & E (2-4, 3) should be below cell 3,4 + equal(2, widgets_above.length); + var widgets_found_above = grid.serialize(widgets_above); + equal(widgets_found_above[1].col, parseInt(this.serialization[4].col)); + equal(widgets_found_above[1].row, parseInt(this.serialization[4].row)); + equal(widgets_found_above[1].size_x, parseInt(this.serialization[4].size_x)); + equal(widgets_found_above[1].size_y, parseInt(this.serialization[4].size_y)); + + + var widgets_below = $([]); + grid.for_each_widget_below(3, 2, function(tcol, trow) { + widgets_below = widgets_below.add(this); + }); + //widget E (2-4, 3) should be above cell 3,2 + equal(1, widgets_below.length); + var widgets_found_below = grid.serialize(widgets_below); + equal(widgets_found_below[0].col, parseInt(this.serialization[4].col)); + equal(widgets_found_below[0].row, parseInt(this.serialization[4].row)); + equal(widgets_found_below[0].size_x, parseInt(this.serialization[4].size_x)); + equal(widgets_found_below[0].size_y, parseInt(this.serialization[4].size_y)); + }); + + test('get_widgets_from', 5, function () { + var input = {col: 2, row: 1, size_x: 3, size_y: 1}; + var grid = this.el.gridster().data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + grid.add_widget('
    2. ', input.size_x, input.size_y, input.col, input.row); + + + var widget = grid.get_widgets_at_cell(input.col, input.row); + // normally you would call parseInt on a return from + // .attr(), but qunit takes care of it for us + equal(widget.attr('data-row'), input.row); + equal(widget.attr('data-col'), input.col); + equal(widget.attr('data-sizex'), input.size_x); + equal(widget.attr('data-sizey'), input.size_y); + }); + + test('get_cells_occupied', 3, function () { + var input = {col: 2, row: 3, size_x: 3, size_y: 1}; + var grid = this.el.gridster().data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + + var cellsUsed = grid.get_cells_occupied(input); + deepEqual(cellsUsed.rows, [3]); + deepEqual(cellsUsed.cols, [2,3,4]); + }); + + test('get_highest_occupied_cell', 1, function () { + var grid = this.el.gridster().data('gridster'); + deepEqual(grid.get_min_col(), 1); + }); + + test('get_highest_occupied_cell', 1, function () { + var grid = this.el.gridster().data('gridster'); + deepEqual(grid.get_highest_occupied_cell(), {col: 3, row: 2}); + }); + + //todo tests to add: + // setup_resize & add_resize_handle + // get_min_col + // shift_cols + // get_widgets_from_DOM dom_to_coords, get_widgets_from_DOM set_dom_grid_height set_dom_grid_width + // generate_stylesheet + // set_num_columns + + + test('add_style_tag', 4, function () { + var grid = this.el.gridster({autogenerate_stylesheet: true}).data('gridster'); + var generatedStyleSheet = $('#gridster-stylesheet'); + notEqual(generatedStyleSheet, null); + ok(generatedStyleSheet.length > 0); + + grid.destroy(); + grid = this.el.gridster({autogenerate_stylesheet: true, namespace: 'qunit'}).data('gridster'); + generatedStyleSheet = $('#gridster-stylesheet-qunit'); + notEqual(generatedStyleSheet, null); + ok(generatedStyleSheet.length > 0); + }); + + test('resize_widget', 4, function () { + this.resizeGrid = [ + {col: 1, row: 1, size_x: 1, size_y: 1}, + {col: 2, row: 1, size_x: 1, size_y: 1}, + {col: 3, row: 1, size_x: 1, size_y: 1}, + {col: 1, row: 2, size_x: 1, size_y: 1}, + {col: 2, row: 2, size_x: 1, size_y: 1}, + {col: 3, row: 2, size_x: 1, size_y: 1}, + {col: 1, row: 3, size_x: 1, size_y: 1}, + {col: 2, row: 3, size_x: 1, size_y: 1}, + {col: 3, row: 3, size_x: 1, size_y: 1} + ]; + + var grid = this.el.gridster({widget_base_dimensions: [100, 55]}).data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + var numBefore = grid.get_num_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + $.each(this.resizeGrid, function () { + grid.add_widget('
    3. ', this.size_x, this.size_y, this.col, this.row); + }); + equal(grid.get_num_widgets(), numBefore + this.resizeGrid.length, 'Loading the widgets to prepare for tests'); + + //check for widgets in the space it will occupy + var widgets = grid.get_widgets_in_range(1,1,2,2); + var numberInSpaceBefore = widgets.length; + equal(numberInSpaceBefore, 4, 'Expect there to be four widgets in the first two rows and cols'); + + //get the widget from 1,1 and resize it. + grid.resize_widget(grid.get_widgets_at_cell(1, 1), 2, 2); + + //check for widgets in the space it will occupy + widgets = grid.get_widgets_in_range(1,1,2,2); + var numberInSpaceAfter = widgets.length; + equal(numberInSpaceAfter, 1, 'Expected a single widget in the expanded area'); + + }); + + test('can serialize correctly', 4, function () { + var grid = this.el.gridster({widget_base_dimensions: [100, 55]}).data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + var numBefore = grid.get_num_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + $.each(this.serialization_small, function () { + grid.add_widget('
    4. ', this.size_x, this.size_y, this.col, this.row); + }); + equal(grid.get_num_widgets(), numBefore + this.serialization_small.length); + var serialized = grid.serialize(); + equal(grid.get_num_widgets(), serialized.length); + deepEqual(serialized, this.serialization_small); + }); + + test('can serialize extended properties', 4, function () { + var input = [{col: 6, row: 3, size_x: 1, size_y: 1}]; + var grid = this.el.gridster({widget_base_dimensions: [100, 55], serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row + };}}).data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + grid.add_widget('
    5. ', input[0].size_x, input[0].size_y, input[0].col, input[0].row); + var serialized = grid.serialize(); + //due to custom serialization, input and output should NOT match + notDeepEqual(serialized, input); + equal(serialized[0].col, 6); + equal(serialized[0].size_x, undefined); + }); + + test('When Adding widgets rows auto condense', 2, function () { + var input = [{col: 6, row: 3, size_x: 1, size_y: 1}]; + var output = [{col: 6, row: 1, size_x: 1, size_y: 1}]; + var grid = this.el.gridster().data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + //make sure we are empty + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + grid.add_widget('
    6. ', input[0].size_x, input[0].size_y, input[0].col, input[0].row); + var serialized = grid.serialize(); + deepEqual(serialized, output); + }); + + test('When Adding widgets rows static placement is supported', 2, function () { + var input = [{col: 6, row: 3, size_x: 1, size_y: 1}]; + var grid = this.el.gridster().data('gridster'); + grid.options.shift_widgets_up = false; + //remove any widgets from the html config + grid.remove_all_widgets(); + //make sure we are empty + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + grid.add_widget('
    7. ', input[0].size_x, input[0].size_y, input[0].col, input[0].row); + var serialized = grid.serialize(); + deepEqual(serialized, input); + }); + + test('When Adding widgets cols are respected', 2, function () { + var input = [{col: 6, row: 1, size_x: 1, size_y: 1}]; + var grid = this.el.gridster().data('gridster'); + //remove any widgets from the html config + grid.remove_all_widgets(); + //make sure we are empty + equal(grid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + grid.add_widget('
    8. ', input[0].size_x, input[0].size_y, input[0].col, input[0].row); + var serialized = grid.serialize(); + deepEqual(serialized, input); + }); + + test('can_move_to', 7, function () { + var input = {col: 6, row: 1, size_x: 1, size_y: 1}; + var defaultGrid = this.el.gridster().data('gridster'); + //remove any widgets from the html config + defaultGrid.remove_all_widgets(); + //make sure we are empty + equal(defaultGrid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + //check with the default config we can place an widget in a skipped col + var canMove = defaultGrid.can_move_to({size_x: input.size_x, size_y: input.size_y}, input.col, input.row); + equal(canMove, true, 'with the default config we can place an widget in a skipped col'); + //check with the default config we can not place an widget in a skipped row + canMove = defaultGrid.can_move_to({size_x: input.size_x, size_y: input.size_y}, input.col, input.row+3); + equal(canMove, true, 'with the default config we can not place an widget in a skipped row'); + defaultGrid.destroy(); + + //now repeat the tests with custom settings + var customGrid = this.el.gridster({max_rows : 2, max_cols : 4}).data('gridster'); + //remove any widgets from the html config + customGrid.remove_all_widgets(); + //make sure we are empty + equal(customGrid.get_num_widgets(), 0, 'Clearing the widgets to prepare for tests'); + //check with the Custom config we can place an widget outside the grid + canMove = customGrid.can_move_to({size_x: input.size_x, size_y: input.size_y}, input.col, input.row); + equal(canMove, false, 'with the Custom config we can place an widget outside the grid'); + //check with the custom config we can not place an widget outside the grid + canMove = customGrid.can_move_to({size_x: input.size_x, size_y: input.size_y}, 1, input.row+3); + equal(canMove, false, 'with the custom config we can not place an widget outside the grid'); + //check to see if we can't move an widget to where there is one + customGrid.add_widget('
    9. ', 1, 1, 1, 1); + canMove = customGrid.can_move_to({size_x: 1, size_y: 1}, 1, 1); + equal(canMove, false, 'we cant move an widget to where there is one'); + + }); + + test('is chainable', 1, function () { + // Not a bad test to run on collection methods. + strictEqual(this.el, this.el.gridster(), 'should be chaninable'); + }); + + test('is Responsive', 1, function () { + var grid = this.el.gridster( + {autogenerate_stylesheet: true, + widget_base_dimensions: ['auto', 'auto'], + max_cols: 4}).data('gridster'); + equal(grid.is_responsive(), true); + }); + + test('Gridster.sort_by_row_asc', 4, function (assert) { + var sorted = Gridster.sort_by_row_asc(this.serialization); + + var result = pickup(sorted, 'name').join(','); + var expected = 'A,B,D,E,C'; + //since the test data contains 3 #1, they could be in any order + ok( result.substring(0,5).indexOf('A') > -1, 'A is found in within the first 3 results'); + ok( result.substring(0,5).indexOf('B') > -1, 'B is found in within the first 3 results'); + ok( result.substring(0,5).indexOf('D') > -1, 'D is found in within the first 3 results'); + //check the last to chars + equal( result.substring(6), expected.substring(6), 'E,C are the last two - In that order'); + }); + + test('Gridster.sort_by_row_and_col_asc', function (assert) { + var sorted = Gridster.sort_by_row_and_col_asc(this.serialization); + + var result = pickup(sorted, 'name').join(','); + var expected = 'A,D,B,E,C'; + assert.equal(result, expected); + }); + + test('Gridster.sort_by_col_asc', function (assert) { + var sorted = Gridster.sort_by_col_asc(this.serialization); + + var result = pickup(sorted, 'name').join(','); + var expected = 'A,E,D,B,C'; + assert.equal(result, expected); + }); + + test('Gridster.sort_by_row_desc',4, function (assert) { + var sorted = Gridster.sort_by_row_desc(this.serialization); + + var result = pickup(sorted, 'name').join(','); + var expected = 'C,E,A,B,D'; + //since the test data contains 3 #1, they could be in any order + ok( result.substring(4).indexOf('A') > -1, 'A is found in within the last 3 results'); + ok( result.substring(4).indexOf('B') > -1, 'B is found in within the last 3 results'); + ok( result.substring(4).indexOf('D') > -1, 'D is found in within the last 3 results'); + //check the last to chars + equal( result.substring(0,3), expected.substring(0,3), 'C,E are the first two - In that order'); + }); + + // errors + test('sort_by_row_asc: Throws not exists property', function (assert) { + assert.throws(function () { + //missing col + var data = [{row: 1, size_x: 1, size_y: 1}, {col: 2, row: 1, size_x: 1, size_y: 1}]; + Gridster.sort_by_row_asc(data); + }, + Error, + 'raise error not exists required property' + ); + }); + + test('sort_by_row_asc: Throws invalid type of value', function (assert) { + var secWidget = {col: 2, row: 1, size_x: 1, size_y: 1}; + // inconvertible types + assert.throws(function () { + //col is not a number + Gridster.sort_by_row_asc([{col: "AAA", row: 1, size_x: 1, size_y: 1}, secWidget]); + }, Error, 'raise error inconvertible types' ); + + // null + assert.throws(function () { + // coll is null + Gridster.sort_by_row_asc([{col: null, row: 1, size_x: 1, size_y: 1}, secWidget]); + }, Error, 'raise error value is null' ); + + // array + assert.throws(function () { + //col does not accept an array + Gridster.sort_by_row_asc([{col: [1, 2, 3], row: 1, size_x: 1, size_y: 1}, secWidget]); + }, Error, 'raise error value is array' ); + + // object + assert.throws(function () { + //col does not accept an onbject + Gridster.sort_by_row_asc([{col: {k: 1}, row: 1, size_x: 1, size_y: 1}, secWidget]); + },Error, 'raise error value is object'); + }); + + // helper + function pickup (data, prop) { + return data.map(function (elm) { + return elm[prop]; + }); + } +}(jQuery)); \ No newline at end of file diff --git a/lib/gridster/test/lib/test_utils.js b/lib/gridster/test/lib/test_utils.js new file mode 100644 index 000000000..b66fd8a89 --- /dev/null +++ b/lib/gridster/test/lib/test_utils.js @@ -0,0 +1,200 @@ +/* global u, SPEED*/ + +'use strict'; + +window.serialization = { + 'default': function() { + return [ + {name: 'A', col: 1, row: 1, size_x: 1, size_y: 2}, + {name: 'B', col: 2, row: 1, size_x: 3, size_y: 2}, + {name: 'C', col: 5, row: 1, size_x: 3, size_y: 2}, + {name: 'D', col: 8, row: 1, size_x: 2, size_y: 1}, + {name: 'E', col: 1, row: 3, size_x: 4, size_y: 1}, + {name: 'F', col: 10, row: 1, size_x: 1, size_y: 2}, + {name: 'G', col: 8, row: 2, size_x: 2, size_y: 1}, + {name: 'H', col: 5, row: 3, size_x: 3, size_y: 2}, + {name: 'I', col: 8, row: 3, size_x: 1, size_y: 1}, + {name: 'J', col: 9, row: 3, size_x: 2, size_y: 2}, + {name: 'K', col: 1, row: 4, size_x: 1, size_y: 3} + ]; + } +}; + + +window.u = { + pick: function(data, prop) { + return data.map(function(elm) { + return elm[prop]; + }); + }, + + getRandomColor: function() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++) { + color += letters[Math.round(Math.random() * 10)]; + } + return color; + }, + + createGridster: function(config, serialize, fromDom) { + var defaults = { + widget_margins: [5, 5], + widget_base_dimensions: [100, 55], + min_cols: 10, + max_cols: 10 + }; + + serialize || (serialize = window.serialization.default()); + + var widgets = []; + $.each(serialize, function(i, w) { + widgets.push(['
    10. ' + w.name + '
    11. ', w.size_x, w.size_y, w.col, w.row]); + }); + + this.$fixture = $('#fixture'); + this.$fixture.html('
        '); + this.$el = $('.gridster > ul'); + + if (fromDom) { + var html = []; + $.each(serialize, function(i, w) { + html.push('
      • ' + w.name + '
      • '); + }); + this.$el.html(html.join('\n')); + } + + this.gridster = this.$el.gridster( + $.extend({}, defaults, config)).data('gridster'); + + if (!fromDom) { + $.each(widgets, function(i, widget) { + this.gridster.add_widget.apply(this.gridster, widget); + }.bind(this)); + } + + this.drag_from_to = u._dragFromTo; + + return this.gridster; + }, + + createEvent: function(type, offset) { + var event = document.createEvent('MouseEvents'); + event.initMouseEvent(type, true, true, window, 0, 0, 0, + offset.left, offset.top, false, false, false, false, 0, null); + + return event; + }, + + dispatchEvent: function(elem, type, event) { + if (elem.dispatchEvent) { + elem.dispatchEvent(event); + } else if (elem.fireEvent) { + elem.fireEvent('on' + type, event); + } + }, + + _dragFromTo: function(fromCoords, toCoords) { + var d = $.Deferred(); + var gridster = this.gridster; + var $el; + + function getMousePos(coords) { + var size = gridster.options.widget_base_dimensions; + var margin = gridster.options.widget_margins; + + var left = ((coords[0] - 1) * size[0]) + (margin[0] * ((coords[0] * 2) - 1)); + var top = ((coords[1] - 1) * size[1]) + (margin[1] * ((coords[1] * 2) - 1)); + + var parentOffset = gridster.$wrapper.offset(); + + return { + left: parentOffset.left + left + 20, + top: parentOffset.top + top + 20 + }; + } + + function addPoint(offset) { + $('').css({ + left: offset.left + 'px', + top: offset.top + 'px', + width: '2px', + height: '2px', + background: 'red', + display: 'inline-block', + position: 'absolute', + zIndex: '9999' + }).appendTo('body'); + } + + var from_offset; + + if (fromCoords instanceof $) { + $el = fromCoords; + var offset = $el.offset(); + from_offset = { + left: offset.left + ($el.width() / 2), + top: offset.top + ($el.height() / 2) + }; + } else { + $el = this.gridster.gridmap[fromCoords[0]][fromCoords[1]]; + from_offset = getMousePos(fromCoords); + } + + if (! $el) { + return; + } + + var to_offset = getMousePos(toCoords); + var el = $el.get(0); + + addPoint(from_offset); + addPoint(to_offset); + + // Simulating drag start + var type = 'mousedown'; + var event = u.createEvent(type, from_offset); + u.dispatchEvent(el, type, event); + + // Simulating drop + type = 'mousemove'; + u.dispatchEvent(el, type, u.createEvent(type, { + top: from_offset.top + 2, + left: from_offset.left + 2 + })); + + this.gridster.$el.on('gridster:dragstop gridster:resizestop', function() { + setTimeout(function() { + d.resolveWith(this); + }.bind(this), SPEED); + }.bind(this)); + + var diff_x = to_offset.left - from_offset.left; + var diff_y = to_offset.top - from_offset.top; + var steps = 10; + var step_x = diff_x / steps; + var step_y = diff_y / steps; + + var tmp_offset = { + left: from_offset.left, + top: from_offset.top + }; + + for (var i = 0; i < steps; i++) { + tmp_offset.left += step_x; + tmp_offset.top += step_y; + addPoint(tmp_offset); + u.dispatchEvent(el, type, u.createEvent(type, tmp_offset)); + } + + u.dispatchEvent(el, type, u.createEvent(type, to_offset)); + addPoint(to_offset); + + // Simulating drag end + type = 'mouseup'; + var dragEndEvent = u.createEvent(type, to_offset); + u.dispatchEvent(el, type, dragEndEvent); + + return d.promise(); + } +}; diff --git a/lib/gridster/test/testsuite.js b/lib/gridster/test/testsuite.js new file mode 100644 index 000000000..fd21103a3 --- /dev/null +++ b/lib/gridster/test/testsuite.js @@ -0,0 +1,27 @@ +'use strict'; +define([ + 'QUnit', + 'jquery', + 'gridster' +], function(QUnit, $, gridster) { + + QUnit.module('Gridster AMD', { + setup: function () { + }, + teardown: function () { + } + }); + + QUnit.test('window.$ should be undefined.', function() { + equal(typeof window.$, 'undefined', 'window.$ should be undefined'); + equal(typeof window.jQuery, 'undefined', 'window.jQuery should be undefined'); + }); + + + QUnit.test('gridster should be initialized.', function() { + $('.wrapper ul').gridster(); + equal($('.wrapper').hasClass('ready'), true, 'Gridster should initialized wrapper.'); + equal($('.wrapper ul li').length, $('.gs-w').length, 'grid elements get a .gs-w class'); + }); + } +); diff --git a/mibs/OBSERVIUM-MIB b/mibs/OBSERVIUM-MIB deleted file mode 100644 index fae41b89c..000000000 --- a/mibs/OBSERVIUM-MIB +++ /dev/null @@ -1,35 +0,0 @@ -OBSERVIUM-MIB DEFINITIONS ::= BEGIN - -IMPORTS - enterprises, MODULE-IDENTITY, OBJECT-TYPE, Counter32, - Gauge32, Counter64, Integer32, TimeTicks, mib-2, - NOTIFICATION-TYPE FROM SNMPv2-SMI - TEXTUAL-CONVENTION, DisplayString, - PhysAddress, TruthValue, RowStatus, - TimeStamp, AutonomousType, TestAndIncr FROM SNMPv2-TC - MODULE-COMPLIANCE, OBJECT-GROUP, - NOTIFICATION-GROUP FROM SNMPv2-CONF - snmpTraps FROM SNMPv2-MIB; - -observium OBJECT IDENTIFIER ::= { enterprises 36602 } -obsObjects OBJECT IDENTIFIER ::= { observium 1 } -obsHostInfo OBJECT IDENTIFIER ::= { obsObjects 1 } - -obsLinuxDistro OBJECT-TYPE - SYNTAX DisplayString (SIZE (0..255)) - MAX-ACCESS read-only - STATUS current - DESCRIPTION - "Linux distribution and version" - ::= { obsHostInfo 2 } - -obsCPUUsage OBJECT-TYPE - SYNTAX INTEGER - ACCESS read-only - STATUS mandatory - DESCRIPTION - "CPU Usage" - ::= { obsHostInfo 3 } - - -END diff --git a/mibs/netonix/NETONIX-SWITCH-MIB b/mibs/netonix/NETONIX-SWITCH-MIB index d473f9c04..2c57e67ca 100644 --- a/mibs/netonix/NETONIX-SWITCH-MIB +++ b/mibs/netonix/NETONIX-SWITCH-MIB @@ -13,7 +13,7 @@ netonixSwitch MODULE-IDENTITY DESCRIPTION "The MIB Module for Netonix Switches." REVISION "9803231700Z" DESCRIPTION "The MIB Module for Netonix Switches." - ::= { enterprises 99999 } + ::= { enterprises 46242 } netonixSwitchGroup OBJECT-GROUP OBJECTS { firmwareVersion, @@ -21,7 +21,8 @@ netonixSwitchGroup OBJECT-GROUP tempDescription, temp, voltageDescription, - voltage + voltage, + poeStatus } STATUS current DESCRIPTION "A collection of objects providing basic instrumentation and control of an SNMPv2 entity." @@ -84,6 +85,40 @@ fanSpeed OBJECT-TYPE DESCRIPTION "Integer reference number (row number) for the fan mib." ::= { fanEntry 2 } +poeStatusTable OBJECT-TYPE + SYNTAX SEQUENCE OF PoEStatus + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "PoE Status per port." + ::= { netonixSwitch 5 } + +poeStatusEntry OBJECT-TYPE + SYNTAX PoeStatusEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "An entry containing poe status." + INDEX { poeStatusIndex } + ::= { poeStatusTable 1 } + +PoEStatusEntry ::= SEQUENCE { + poeStatusIndex Integer32, + poeStatus DisplayString +} + +poeStatusIndex OBJECT-TYPE + SYNTAX Integer32 (0..65535) + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "Integer reference number (row number) for the poe status." + ::= { poeStatusEntry 1 } + +poeStatus OBJECT-TYPE + SYNTAX DisplayString (SIZE (0..255)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION "poe status." + ::= { poeStatusEntry 2 } + tempTable OBJECT-TYPE SYNTAX SEQUENCE OF TempEntry MAX-ACCESS not-accessible diff --git a/notifications.php b/notifications.php new file mode 100644 index 000000000..7c3acb8ea --- /dev/null +++ b/notifications.php @@ -0,0 +1,100 @@ + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +/** + * Notification Poller + * @author Daniel Preussker + * @copyright 2015 Daniel Preussker, QuxLabs UG + * @license GPL + * @package LibreNMS + * @subpackage Notifications + */ + +require_once 'includes/defaults.inc.php'; +require_once 'config.php'; +require_once $config['install_dir'].'/includes/definitions.inc.php'; +require_once $config['install_dir'].'/includes/functions.php'; + +/** + * Pull notifications from remotes + * @return array Notifications + */ +function get_notifications() { + global $config; + $obj = array(); + foreach ($config['notifications'] as $name=>$url) { + echo '[ '.date('r').' ] '.$url.' '; + $feed = json_decode(json_encode(simplexml_load_string(file_get_contents($url))),true); + if (isset($feed['channel'])) { + $feed = parse_rss($feed); + } else { + $feed = parse_atom($feed); + } + array_walk($feed,function(&$items,$key,$url) { $items['source'] = $url; },$url); + $obj = array_reverse(array_merge($obj,$feed)); + echo '('.sizeof($obj).')'.PHP_EOL; + } + return $obj; +} + +/** + * Post notifications to users + * @return null + */ +function post_notifications() { + $notifs = get_notifications(); + echo '[ '.date('r').' ] Updating DB '; + foreach ($notifs as $notif) { + if (dbFetchCell('select 1 from notifications where checksum = ?',array($notif['checksum'])) != 1 && dbInsert('notifications',$notif) > 0) { + echo '.'; + } + } + echo ' Done'; + echo PHP_EOL; +} + +/** + * Parse RSS + * @param array $feed RSS Object + * @return array Parsed Object + */ +function parse_rss($feed) { + $obj = array(); + if( !array_key_exists('0',$feed['channel']['item']) ) { + $feed['channel']['item'] = array( $feed['channel']['item'] ); + } + foreach ($feed['channel']['item'] as $item) { + $obj[] = array('title'=>$item['title'],'body'=>$item['description'],'checksum'=>hash('sha512',$item['title'].$item['description'])); + } + return $obj; +} + +/** + * Parse Atom + * @param array $feed Atom Object + * @return array Parsed Object + */ +function parse_atom($feed) { + $obj = array(); + if( !array_key_exists('0',$feed['entry']) ) { + $feed['entry'] = array( $feed['entry'] ); + } + foreach ($feed['entry'] as $item) { + $obj[] = array('title'=>$item['title'],'body'=>$item['content'],'checksum'=>hash('sha512',$item['title'].$item['content'])); + } + return $obj; +} + +post_notifications(); diff --git a/scripts/distro b/scripts/distro index ecfda5c4b..96cbbe75f 100755 --- a/scripts/distro +++ b/scripts/distro @@ -60,6 +60,10 @@ elif [ "${OS}" = "Linux" ] ; then elif [ -f /etc/pld-release ] ; then DIST=$(cat /etc/pld-release) REV="" + + elif [ -f /etc/SuSE-release ] ; then + DIST=$(echo SLES $(grep VERSION /etc/SuSE-release | cut -d = -f 2 | tr -d " ")) + REV=$(echo SP$(grep PATCHLEVEL /etc/SuSE-release | cut -d = -f 2 | tr -d " ")) fi if [ -f /etc/lsb-release -a "${IGNORE_LSB}" != 1 ] ; then diff --git a/sql-schema/077.sql b/sql-schema/077.sql index b4106e952..3f84fd9f0 100644 --- a/sql-schema/077.sql +++ b/sql-schema/077.sql @@ -1 +1,2 @@ -INSERT INTO config (config_name,config_value,config_default,config_descr,config_group,config_group_order,config_sub_group,config_sub_group_order,config_hidden,config_disabled) values ('rrdtool','/usr/bin/rrdtool','/usr/bin/rrdtool','Path to rrdtool','external',0,'rrdtool',0,'0','0'), ('rrdtool_tune','false','false','Auto tune maximum value for rrd port files','external',0,'rrdtool',0,'0','0'); +CREATE TABLE `notifications` ( `notifications_id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL DEFAULT '', `body` text NOT NULL, `source` varchar(255) NOT NULL DEFAULT '', `checksum` varchar(128) NOT NULL, PRIMARY KEY (`notifications_id`), UNIQUE KEY `checksum` (`checksum`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `notifications_attribs` ( `attrib_id` int(11) NOT NULL AUTO_INCREMENT, `notifications_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `key` varchar(255) NOT NULL DEFAULT '', `value` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`attrib_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql-schema/078.sql b/sql-schema/078.sql new file mode 100644 index 000000000..8ef025e41 --- /dev/null +++ b/sql-schema/078.sql @@ -0,0 +1 @@ +INSERT INTO config (config_name,config_value,config_default,config_descr,config_group,config_group_order,config_sub_group,config_sub_group_order,config_hidden,config_disabled) values ('alert.transports.clickatell.token','','','Clickatell access token','alerting',0,'transports',0,'0','0'),('alert.transports.playsms.url','','','PlaySMS API URL','alerting',0,'transports',0,'0','0'),('alert.transports.playsms.user','','','PlaySMS User','alerting',0,'transports',0,'0','0'),('alert.transports.playsms.from','','','PlaySMS From','alerting',0,'transports',0,'0','0'),('alert.transports.playsms.token','','','PlaySMS Token','alerting',0,'transports',0,'0','0'), ('alert.transports.victorops.url','','','VictorOps Post URL - Replace routing_key!','alerting',0,'transports',0,'0','0'); diff --git a/sql-schema/079.sql b/sql-schema/079.sql new file mode 100644 index 000000000..b4106e952 --- /dev/null +++ b/sql-schema/079.sql @@ -0,0 +1 @@ +INSERT INTO config (config_name,config_value,config_default,config_descr,config_group,config_group_order,config_sub_group,config_sub_group_order,config_hidden,config_disabled) values ('rrdtool','/usr/bin/rrdtool','/usr/bin/rrdtool','Path to rrdtool','external',0,'rrdtool',0,'0','0'), ('rrdtool_tune','false','false','Auto tune maximum value for rrd port files','external',0,'rrdtool',0,'0','0'); diff --git a/validate.php b/validate.php index dbfc89c4a..2c0316587 100755 --- a/validate.php +++ b/validate.php @@ -93,6 +93,13 @@ if(strstr($strict_mode, 'STRICT_TRANS_TABLES')) { print_warn('You have MySQL STRICT_TRANS_TABLES enabled, it is advisable to disable this until full support has been added: https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html'); } +// Test for MySQL InnoDB buffer size +$innodb_buffer = innodb_buffer_check(); +if ($innodb_buffer['used'] > $innodb_buffer['size']) { + print_fail('Your Innodb buffer size is not big enough...'); + echo warn_innodb_buffer($innodb_buffer); +} + // Test transports if ($config['alerts']['email']['enable'] === true) { print_warn('You have the old alerting system enabled - this is to be deprecated on the 1st of June 2015: https://groups.google.com/forum/#!topic/librenms-project/1llxos4m0p4');