diff --git a/Makefile b/Makefile index 5fa4f872..6e1ac5d9 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,7 @@ reset: clean reload-db: @php artisan database:create --reset @php artisan migrate:refresh --seed + @php artisan phpvms:import app/Database/seeds/sample.yml @php artisan phpvms:navdata .PHONY: tests diff --git a/app/Console/Commands/DevCommands.php b/app/Console/Commands/DevCommands.php index e2abc7dd..46fb2012 100644 --- a/app/Console/Commands/DevCommands.php +++ b/app/Console/Commands/DevCommands.php @@ -11,17 +11,9 @@ use App\Models\Pirep; class DevCommands extends BaseCommand { - protected $signature = 'phpvms {cmd} {--file=?}'; + protected $signature = 'phpvms {cmd}'; protected $description = 'Developer commands'; - protected $dbSvc; - - public function __construct(DatabaseService $dbSvc) - { - parent::__construct(); - $this->dbSvc = $dbSvc; - } - /** * Run dev related commands */ @@ -37,7 +29,6 @@ class DevCommands extends BaseCommand $commands = [ 'clear-acars' => 'clearAcars', 'compile-assets' => 'compileAssets', - 'import' => 'importYaml', ]; if(!array_key_exists($command, $commands)) { @@ -76,12 +67,4 @@ class DevCommands extends BaseCommand $this->runCommand('npm update'); $this->runCommand('npm run dev'); } - - /** - * Import data from a YAML file - */ - protected function importYaml() - { - $this->info('importing '. $this->argument('file')); - } } diff --git a/app/Console/Commands/ImportCommand.php b/app/Console/Commands/ImportCommand.php new file mode 100644 index 00000000..bd5a5be3 --- /dev/null +++ b/app/Console/Commands/ImportCommand.php @@ -0,0 +1,53 @@ +dbSvc = $dbSvc; + } + + /** + * Run dev related commands + */ + public function handle() + { + $files = $this->argument('files'); + if(empty($files)) { + $this->error('No files to import specified!'); + exit(); + } + + $ignore_errors = true; + /*$ignore_errors = $this->option('ignore_errors'); + if(!$ignore_errors) { + $ignore_errors = false; + }*/ + + foreach($files as $file) { + if(!file_exists($file)) { + $this->error('File ' . $file .' doesn\'t exist'); + exit; + } + + $this->info('Importing ' . $file); + + $imported = $this->dbSvc->seed_from_yaml_file($file, $ignore_errors); + foreach($imported as $table => $count) { + $this->info('Imported '.$count.' records from "'.$table.'"'); + } + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 8da57da8..829bf8ac 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel Commands\AcarsReplay::class, Commands\CreateDatabase::class, Commands\DevCommands::class, + Commands\ImportCommand::class, Commands\Install::class, Commands\NavdataCommand::class, ]; diff --git a/app/Database/migrations/2017_06_21_165410_create_ranks_table.php b/app/Database/migrations/2017_06_21_165410_create_ranks_table.php index 75f9e682..a01835a6 100644 --- a/app/Database/migrations/2017_06_21_165410_create_ranks_table.php +++ b/app/Database/migrations/2017_06_21_165410_create_ranks_table.php @@ -1,6 +1,6 @@ unique('name'); }); + + /** + * Initial required data... + */ + $ranks = [ + [ + 'id' => 1, + 'name' => 'New Pilot', + 'hours' => 0, + ] + ]; + + $this->addData('ranks', $ranks); } /** diff --git a/app/Database/seeds/dev.yml b/app/Database/seeds/dev.yml index e4f843ca..55836add 100644 --- a/app/Database/seeds/dev.yml +++ b/app/Database/seeds/dev.yml @@ -1,92 +1,7 @@ - -airlines: - - id: 1 - icao: VMS - iata: VM - name: phpvms airlines - active: 1 - created_at: now - updated_at: now - -users: - - id: 1 - name: Admin User - email: admin@phpvms.net - password: admin - api_key: testadminapikey - airline_id: 1 - rank_id: 1 - home_airport_id: KAUS - curr_airport_id: KJFK - last_pirep_id: pirepid_3 - flights: 3 - flight_time: 43200 - timezone: America/Chicago - state: 1 - created_at: now - updated_at: now - - id: 2 - name: Carla Walters - email: carla.walters68@example.com - password: admin - api_key: testuserapikey1 - airline_id: 1 - rank_id: 1 - home_airport_id: KJFK - curr_airport_id: KJFK - flights: 1 - flight_time: 43200 - created_at: now - updated_at: now - state: 0 - - id: 3 - name: Raymond Pearson - email: raymond.pearson56@example.com - password: admin - api_key: testuserapikey2 - airline_id: 1 - rank_id: 1 - home_airport_id: KJFK - curr_airport_id: KAUS - flights: 1 - flight_time: 43200 - created_at: now - updated_at: now - state: 1 - -role_user: - - user_id: 1 - role_id: 1 - user_type: App\Models\User - - user_id: 1 - role_id: 2 - user_type: App\Models\User - - user_id: 2 - role_id: 2 - user_type: App\Models\User - - user_id: 3 - role_id: 2 - user_type: App\Models\User - -# ranks -ranks: - - id: 1 - name: New Pilot - hours: 0 - - id: 2 - name: Junior First Officer - hours: 10 - - id: 3 - name: First Officer - hours: 15 - auto_approve_acars: 1 - auto_approve_manual: 1 - - id: 4 - name: Senior Captain - hours: 20 - auto_approve_acars: 1 - auto_approve_manual: 1 - auto_promote: 0 +# +# Initial minimal data required. You probably don't +# want to modify or erase any of this here +# airports: - id: KAUS @@ -98,233 +13,3 @@ airports: lat: 30.1945278 lon: -97.6698889 tz: America/Chicago - - id: KJFK - iata: JFK - icao: KJFK - name: John F Kennedy - location: New York, New York, USA - country: United States - lat: 40.6399257 - lon: -73.7786950 - tz: America/New_York - - id: KBWI - iata: BWI - icao: KBWI - name: Baltimore/Washington International Thurgood Marshall Airport - location: Baltimore, MD - country: United States - lat: 39.1754 - lon: -76.6683 - tz: America/New_York - - id: KIAH - iata: IAH - icao: KIAH - name: George Bush Intercontinental Houston Airport - location: Houston, TX - country: United States - lat: 29.9844 - lon: -95.3414 - tz: America/Chicago - - id: KORD - iata: ORD - icao: KORD - name: Chicago O'Hare International Airport - location: Chicago, IL - country: United States - lat: 41.9786 - lon: -87.9048 - tz: America/Chicago - - id: KDFW - iata: DFW - icao: KDFW - name: Dallas Fort Worth International Airport - location: Dallas, TX - country: United States - lat: 32.8968 - lon: -97.038 - tz: America/Chicago - - id: EFHK - iata: HEL - icao: EFHK - name: Helsinki Vantaa Airport - location: Helsinki - country: Finland - lat: 60.3172 - lon: 24.9633 - tz: Europe/Helsinki - - id: EGLL - iata: LHR - icao: EGLL - name: London Heathrow - location: London, England - lat: 51.4775 - lon: -0.4614 - tz: Europe/London - -# -aircraft: - - id: 1 - subfleet_id: 1 - name: Boeing 747-400 - registration: NC17 - tail_number: 17 - - id: 2 - subfleet_id: 2 - name: Boeing 777-200 - registration: NC20 - tail_number: 20 - -#aircraft_rank: -# - aircraft_id: 1 -# rank_id: 1 -# - aircraft_id: 1 -# rank_id: 2 -# acars_pay: 100 -# manual_pay: 50 - -fares: - - id: 1 - code: Y - name: Economy - price: 100 - capacity: 200 - - id: 2 - code: B - name: Business - price: 500 - capacity: 10 - - id: 3 - code: F - name: First-Class - price: 800 - capacity: 5 - -subfleets: - - id: 1 - airline_id: 1 - name: 747-400 Winglets - type: 744W - - id: 2 - airline_id: 1 - name: 777-200 LR - type: 772-LR - -# add a few mods to aircraft and fares -subfleet_fare: - - # Fare classes on the 747 - - subfleet_id: 1 - fare_id: 1 - price: 200 - capacity: 400 - - subfleet_id: 1 - fare_id: 2 - capacity: 20 - - subfleet_id: 1 - fare_id: 3 - price: 1000 - capacity: 10 - - # Fare classes on the 777 - - subfleet_id: 2 - fare_id: 1 - - subfleet_id: 2 - fare_id: 3 - capacity: 10 - -subfleet_flight: - - subfleet_id: 1 - flight_id: flightid_1 - -flights: - - id: flightid_1 - airline_id: 1 - flight_number: 100 - dpt_airport_id: KAUS - arr_airport_id: KJFK - route: KAUS SID TNV J87 IAH J2 LCH J22 MEI J239 ATL J52 AJFEB J14 BYJAC Q60 JAXSN J14 COLIN J61 HUBBS J55 SIE STAR KJFK - dpt_time: 6PM CST - arr_time: 11PM EST - created_at: NOW - updated_at: NOW - - id: flightid_2 - airline_id: 1 - flight_number: 6028 - dpt_airport_id: KIAH - arr_airport_id: KAUS - dpt_time: 9AM CST - arr_time: 1030AM CST - route: PITZZ4 MNURE WLEEE4 - created_at: NOW - updated_at: NOW - -flight_fields: - - id: 1 - flight_id: flightid_1 - name: cost index - value: 80 - - id: 2 - flight_id: flightid_2 - name: cost index - value: 100 - -user_bids: - - id: 100 - user_id: 1 - flight_id: flightid_1 - - id: 101 - user_id: 1 - flight_id: flightid_3 - -pireps: - - id: pirepid_1 - user_id: 1 - airline_id: 1 - flight_id: flightid_1 - aircraft_id: 1 - dpt_airport_id: KAUS - arr_airport_id: KJFK - flight_time: 180 # 6 hours - state: 1 - route: PLMMR2 SPA Q22 BEARI FAK PHLBO3 - notes: just a pilot report - created_at: NOW - updated_at: NOW - - id: pirepid_2 - user_id: 1 - airline_id: 1 - flight_id: flightid_2 - aircraft_id: 1 - dpt_airport_id: KJFK - arr_airport_id: KAUS - flight_time: 180 # 6 hours - state: 1 - route: PLMMR2 SPA Q22 BEARI FAK PHLBO3 - notes: just a pilot report - created_at: NOW - updated_at: NOW - - id: pirepid_3 - user_id: 1 - airline_id: 1 - flight_id: flightid_2 - aircraft_id: 1 - dpt_airport_id: KJFK - arr_airport_id: KAUS - flight_time: 180 # 6 hours - state: 1 - route: PLMMR2 SPA Q22 BEARI FAK PHLBO3 - notes: just a pilot report - created_at: NOW - updated_at: NOW - -pirep_fields: - - id: 1 - name: arrival gate - required: 0 - -pirep_field_values: - - id: 1 - pirep_id: pirepid_1 - name: arrival gate - value: B14 - source: manual diff --git a/app/Database/seeds/sample.yml b/app/Database/seeds/sample.yml new file mode 100644 index 00000000..df76b459 --- /dev/null +++ b/app/Database/seeds/sample.yml @@ -0,0 +1,327 @@ + +airlines: + - id: 1 + icao: VMS + iata: VM + name: phpvms airlines + active: 1 + created_at: now + updated_at: now + +users: + - id: 1 + name: Admin User + email: admin@phpvms.net + password: admin + api_key: testadminapikey + airline_id: 1 + rank_id: 1 + home_airport_id: KAUS + curr_airport_id: KJFK + last_pirep_id: pirepid_3 + flights: 3 + flight_time: 43200 + timezone: America/Chicago + state: 1 + created_at: now + updated_at: now + - id: 2 + name: Carla Walters + email: carla.walters68@example.com + password: admin + api_key: testuserapikey1 + airline_id: 1 + rank_id: 1 + home_airport_id: KJFK + curr_airport_id: KJFK + flights: 1 + flight_time: 43200 + created_at: now + updated_at: now + state: 0 + - id: 3 + name: Raymond Pearson + email: raymond.pearson56@example.com + password: admin + api_key: testuserapikey2 + airline_id: 1 + rank_id: 1 + home_airport_id: KJFK + curr_airport_id: KAUS + flights: 1 + flight_time: 43200 + created_at: now + updated_at: now + state: 1 + +role_user: + - user_id: 1 + role_id: 1 + user_type: App\Models\User + - user_id: 1 + role_id: 2 + user_type: App\Models\User + - user_id: 2 + role_id: 2 + user_type: App\Models\User + - user_id: 3 + role_id: 2 + user_type: App\Models\User + +# ranks +ranks: + - id: 2 + name: Junior First Officer + hours: 10 + - id: 3 + name: First Officer + hours: 15 + auto_approve_acars: 1 + auto_approve_manual: 1 + - id: 4 + name: Senior Captain + hours: 20 + auto_approve_acars: 1 + auto_approve_manual: 1 + auto_promote: 0 + +airports: + - id: KAUS + iata: AUS + icao: KAUS + name: Austin-Bergstrom + location: Austin, Texas, USA + country: United States + lat: 30.1945278 + lon: -97.6698889 + tz: America/Chicago + - id: KJFK + iata: JFK + icao: KJFK + name: John F Kennedy + location: New York, New York, USA + country: United States + lat: 40.6399257 + lon: -73.7786950 + tz: America/New_York + - id: KBWI + iata: BWI + icao: KBWI + name: Baltimore/Washington International Thurgood Marshall Airport + location: Baltimore, MD + country: United States + lat: 39.1754 + lon: -76.6683 + tz: America/New_York + - id: KIAH + iata: IAH + icao: KIAH + name: George Bush Intercontinental Houston Airport + location: Houston, TX + country: United States + lat: 29.9844 + lon: -95.3414 + tz: America/Chicago + - id: KORD + iata: ORD + icao: KORD + name: Chicago O'Hare International Airport + location: Chicago, IL + country: United States + lat: 41.9786 + lon: -87.9048 + tz: America/Chicago + - id: KDFW + iata: DFW + icao: KDFW + name: Dallas Fort Worth International Airport + location: Dallas, TX + country: United States + lat: 32.8968 + lon: -97.038 + tz: America/Chicago + - id: EFHK + iata: HEL + icao: EFHK + name: Helsinki Vantaa Airport + location: Helsinki + country: Finland + lat: 60.3172 + lon: 24.9633 + tz: Europe/Helsinki + - id: EGLL + iata: LHR + icao: EGLL + name: London Heathrow + location: London, England + lat: 51.4775 + lon: -0.4614 + tz: Europe/London + +# +aircraft: + - id: 1 + subfleet_id: 1 + name: Boeing 747-400 + registration: NC17 + tail_number: 17 + - id: 2 + subfleet_id: 2 + name: Boeing 777-200 + registration: NC20 + tail_number: 20 + +#aircraft_rank: +# - aircraft_id: 1 +# rank_id: 1 +# - aircraft_id: 1 +# rank_id: 2 +# acars_pay: 100 +# manual_pay: 50 + +fares: + - id: 1 + code: Y + name: Economy + price: 100 + capacity: 200 + - id: 2 + code: B + name: Business + price: 500 + capacity: 10 + - id: 3 + code: F + name: First-Class + price: 800 + capacity: 5 + +subfleets: + - id: 1 + airline_id: 1 + name: 747-400 Winglets + type: 744W + - id: 2 + airline_id: 1 + name: 777-200 LR + type: 772-LR + +# add a few mods to aircraft and fares +subfleet_fare: + + # Fare classes on the 747 + - subfleet_id: 1 + fare_id: 1 + price: 200 + capacity: 400 + - subfleet_id: 1 + fare_id: 2 + capacity: 20 + - subfleet_id: 1 + fare_id: 3 + price: 1000 + capacity: 10 + + # Fare classes on the 777 + - subfleet_id: 2 + fare_id: 1 + - subfleet_id: 2 + fare_id: 3 + capacity: 10 + +subfleet_flight: + - subfleet_id: 1 + flight_id: flightid_1 + +flights: + - id: flightid_1 + airline_id: 1 + flight_number: 100 + dpt_airport_id: KAUS + arr_airport_id: KJFK + route: KAUS SID TNV J87 IAH J2 LCH J22 MEI J239 ATL J52 AJFEB J14 BYJAC Q60 JAXSN J14 COLIN J61 HUBBS J55 SIE STAR KJFK + dpt_time: 6PM CST + arr_time: 11PM EST + created_at: NOW + updated_at: NOW + - id: flightid_2 + airline_id: 1 + flight_number: 6028 + dpt_airport_id: KIAH + arr_airport_id: KAUS + dpt_time: 9AM CST + arr_time: 1030AM CST + route: PITZZ4 MNURE WLEEE4 + created_at: NOW + updated_at: NOW + +flight_fields: + - id: 1 + flight_id: flightid_1 + name: cost index + value: 80 + - id: 2 + flight_id: flightid_2 + name: cost index + value: 100 + +user_bids: + - id: 100 + user_id: 1 + flight_id: flightid_1 + - id: 101 + user_id: 1 + flight_id: flightid_3 + +pireps: + - id: pirepid_1 + user_id: 1 + airline_id: 1 + flight_id: flightid_1 + aircraft_id: 1 + dpt_airport_id: KAUS + arr_airport_id: KJFK + flight_time: 180 # 6 hours + state: 1 + route: PLMMR2 SPA Q22 BEARI FAK PHLBO3 + notes: just a pilot report + created_at: NOW + updated_at: NOW + - id: pirepid_2 + user_id: 1 + airline_id: 1 + flight_id: flightid_2 + aircraft_id: 1 + dpt_airport_id: KJFK + arr_airport_id: KAUS + flight_time: 180 # 6 hours + state: 1 + route: PLMMR2 SPA Q22 BEARI FAK PHLBO3 + notes: just a pilot report + created_at: NOW + updated_at: NOW + - id: pirepid_3 + user_id: 1 + airline_id: 1 + flight_id: flightid_2 + aircraft_id: 1 + dpt_airport_id: KJFK + arr_airport_id: KAUS + flight_time: 180 # 6 hours + state: 1 + route: PLMMR2 SPA Q22 BEARI FAK PHLBO3 + notes: just a pilot report + created_at: NOW + updated_at: NOW + +pirep_fields: + - id: 1 + name: arrival gate + required: 0 + +pirep_field_values: + - id: 1 + pirep_id: pirepid_1 + name: arrival gate + value: B14 + source: manual diff --git a/app/Http/Controllers/Admin/AircraftController.php b/app/Http/Controllers/Admin/AircraftController.php index f5178f1d..2bc274f2 100644 --- a/app/Http/Controllers/Admin/AircraftController.php +++ b/app/Http/Controllers/Admin/AircraftController.php @@ -30,7 +30,7 @@ class AircraftController extends BaseController $aircraft = $this->aircraftRepository->all(); return view('admin.aircraft.index', [ - 'aircraft', $aircraft + 'aircraft' => $aircraft ]); } diff --git a/app/Services/DatabaseService.php b/app/Services/DatabaseService.php index d485579b..a01c5da3 100644 --- a/app/Services/DatabaseService.php +++ b/app/Services/DatabaseService.php @@ -28,16 +28,32 @@ class DatabaseService extends BaseService return Carbon::now('UTC')->format('Y-m-d H:i:s'); } - public function seed_from_yaml_file($yaml_file) + /** + * @param $yaml_file + * @param bool $ignore_errors + * @return array + * @throws \Exception + */ + public function seed_from_yaml_file($yaml_file, $ignore_errors=false): array { $yml = file_get_contents($yaml_file); - $this->seed_from_yaml($yml); + return $this->seed_from_yaml($yml, $ignore_errors); } - public function seed_from_yaml($yml) + /** + * @param $yml + * @param bool $ignore_errors + * @return array + * @throws \Exception + */ + public function seed_from_yaml($yml, $ignore_errors=false): array { + $imported = []; $yml = Yaml::parse($yml); foreach ($yml as $table => $rows) { + + $imported[$table] = 0; + foreach ($rows as $row) { # see if this table uses a UUID as the PK @@ -60,12 +76,19 @@ class DatabaseService extends BaseService } } - #try { + try { DB::table($table)->insert($row); - #} catch(QueryException $e) { - # Log::info($e->getMessage()); - #} + ++$imported[$table]; + } catch(QueryException $e) { + if($ignore_errors) { + continue; + } + + throw $e; + } } } + + return $imported; } } diff --git a/package-lock.json b/package-lock.json index 979c91b0..615c608d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,9 +2,6 @@ "requires": true, "lockfileVersion": 1, "dependencies": { - "Leaflet.Geodesic": { - "version": "git+https://git@github.com/henrythasler/Leaflet.Geodesic.git#c5dd6d6a0ee394d0c274d2a3a09d69a11fc11b8b" - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1192,6 +1189,11 @@ "hoek": "2.16.3" } }, + "bootstrap-editable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bootstrap-editable/-/bootstrap-editable-1.0.1.tgz", + "integrity": "sha1-RFpnG1q1/td+jH9OX/vHlsYLUCM=" + }, "bootstrap-sass": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/bootstrap-sass/-/bootstrap-sass-3.3.7.tgz", @@ -10918,6 +10920,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "x-editable": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/x-editable/-/x-editable-1.5.1.tgz", + "integrity": "sha1-Ltu4kR7yxdYfY/BrDPAgvg/MWEk=" + }, "xml-char-classes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz", diff --git a/package.json b/package.json index ade272b2..ef37cdf1 100755 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "pjax": "^0.2.4", "popper.js": "^1.13.0", "rivets": "0.9.6", - "select2": "^4.0.5" + "select2": "^4.0.5", + "x-editable": "1.5.1" } } diff --git a/public/assets/admin/css/vendor.min.css b/public/assets/admin/css/vendor.min.css index 8df04d54..4c53848e 100644 --- a/public/assets/admin/css/vendor.min.css +++ b/public/assets/admin/css/vendor.min.css @@ -16416,6 +16416,670 @@ a.editable-click.editable-disabled:hover { background-size: 176px 22px; } } +/*! X-editable - v1.5.1 +* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery +* http://github.com/vitalets/x-editable +* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ +.editableform { + margin-bottom: 0; /* overwrites bootstrap margin */ +} + +.editableform .control-group { + margin-bottom: 0; /* overwrites bootstrap margin */ + white-space: nowrap; /* prevent wrapping buttons on new line */ + line-height: 20px; /* overwriting bootstrap line-height. See #133 */ +} + +/* + BS3 width:1005 for inputs breaks editable form in popup + See: https://github.com/vitalets/x-editable/issues/393 +*/ +.editableform .form-control { + width: auto; +} + +.editable-buttons { + display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ + vertical-align: top; + margin-left: 7px; + /* inline-block emulation for IE7*/ + zoom: 1; + *display: inline; +} + +.editable-buttons.editable-buttons-bottom { + display: block; + margin-top: 7px; + margin-left: 0; +} + +.editable-input { + vertical-align: top; + display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ + width: auto; /* bootstrap-responsive has width: 100% that breakes layout */ + white-space: normal; /* reset white-space decalred in parent*/ + /* display-inline emulation for IE7*/ + zoom: 1; + *display: inline; +} + +.editable-buttons .editable-cancel { + margin-left: 7px; +} + +/*for jquery-ui buttons need set height to look more pretty*/ +.editable-buttons button.ui-button-icon-only { + height: 24px; + width: 30px; +} + +.editableform-loading { + background: url('../img/loading.gif') center center no-repeat; + height: 25px; + width: auto; + min-width: 25px; +} + +.editable-inline .editableform-loading { + background-position: left 5px; +} + + .editable-error-block { + max-width: 300px; + margin: 5px 0 0 0; + width: auto; + white-space: normal; +} + +/*add padding for jquery ui*/ +.editable-error-block.ui-state-error { + padding: 3px; +} + +.editable-error { + color: red; +} + +/* ---- For specific types ---- */ + +.editableform .editable-date { + padding: 0; + margin: 0; + float: left; +} + +/* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */ +.editable-inline .add-on .icon-th { + margin-top: 3px; + margin-left: 1px; +} + + +/* checklist vertical alignment */ +.editable-checklist label input[type="checkbox"], +.editable-checklist label span { + vertical-align: middle; + margin: 0; +} + +.editable-checklist label { + white-space: nowrap; +} + +/* set exact width of textarea to fit buttons toolbar */ +.editable-wysihtml5 { + width: 566px; + height: 250px; +} + +/* clear button shown as link in date inputs */ +.editable-clear { + clear: both; + font-size: 0.9em; + text-decoration: none; + text-align: right; +} + +/* IOS-style clear button for text inputs */ +.editable-clear-x { + background: url('../img/clear.png') center center no-repeat; + display: block; + width: 13px; + height: 13px; + position: absolute; + opacity: 0.6; + z-index: 100; + + top: 50%; + right: 6px; + margin-top: -6px; + +} + +.editable-clear-x:hover { + opacity: 1; +} + +.editable-pre-wrapped { + white-space: pre-wrap; +} +.editable-container.editable-popup { + max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ +} + +.editable-container.popover { + width: auto; /* without this rule popover does not stretch */ +} + +.editable-container.editable-inline { + display: inline-block; + vertical-align: middle; + width: auto; + /* inline-block emulation for IE7*/ + zoom: 1; + *display: inline; +} + +.editable-container.ui-widget { + font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */ + z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */ +} +.editable-click, +a.editable-click, +a.editable-click:hover { + text-decoration: none; + border-bottom: dashed 1px #0088cc; +} + +.editable-click.editable-disabled, +a.editable-click.editable-disabled, +a.editable-click.editable-disabled:hover { + color: #585858; + cursor: default; + border-bottom: none; +} + +.editable-empty, .editable-empty:hover, .editable-empty:focus{ + font-style: italic; + color: #DD1144; + /* border-bottom: none; */ + text-decoration: none; +} + +.editable-unsaved { + font-weight: bold; +} + +.editable-unsaved:after { +/* content: '*'*/ +} + +.editable-bg-transition { + -webkit-transition: background-color 1400ms ease-out; + -moz-transition: background-color 1400ms ease-out; + -o-transition: background-color 1400ms ease-out; + -ms-transition: background-color 1400ms ease-out; + transition: background-color 1400ms ease-out; +} + +/*see https://github.com/vitalets/x-editable/issues/139 */ +.form-horizontal .editable +{ + padding-top: 5px; + display:inline-block; +} + + +/*! + * Datepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; + /*.dow { + border-top: 1px solid #ddd !important; + }*/ + +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 6px; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + position: absolute; + top: -6px; + left: 7px; +} +.datepicker > div { + display: none; +} +.datepicker.days div.datepicker-days { + display: block; +} +.datepicker.months div.datepicker-months { + display: block; +} +.datepicker.years div.datepicker-years { + display: block; +} +.datepicker table { + margin: 0; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); + background-image: linear-gradient(top, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(top, #f3c17a, #f3e97a); + background-image: linear-gradient(top, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(top, #b3b3b3, #808080); + background-image: -ms-linear-gradient(top, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(top, #b3b3b3, #808080); + background-image: -o-linear-gradient(top, #b3b3b3, #808080); + background-image: linear-gradient(top, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker th.datepicker-switch { + width: 145px; +} +.datepicker thead tr:first-child th, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker thead tr:first-child th:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.datepicker thead tr:first-child th.cw { + cursor: default; + background-color: transparent; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + display: block; + cursor: pointer; + width: 16px; + height: 16px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 18px; + padding: 4px 5px; + font-weight: normal; + line-height: 18px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} + /*! ========================================================= diff --git a/public/assets/admin/js/vendor.js b/public/assets/admin/js/vendor.js index 023291f2..c5e691e7 100644 --- a/public/assets/admin/js/vendor.js +++ b/public/assets/admin/js/vendor.js @@ -27337,6 +27337,2370 @@ if ( !noGlobal ) { return jQuery; } ); +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.5 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.5 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.5' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.5 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.5' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.5 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.5' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.5 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.5' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.5 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.5' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger('shown.bs.dropdown', relatedTarget) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.5 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.5' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.5 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.5' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '
text|textarea|select|date|checklist
+
+ @property type
+ @type string
+ @default 'text'
+ **/
+ type: 'text',
+ /**
+ Url for submit, e.g. '/post'
+ If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
+
+ @property url
+ @type string|function
+ @default null
+ @example
+ url: function(params) {
+ var d = new $.Deferred;
+ if(params.value === 'abc') {
+ return d.reject('error message'); //returning error via deferred object
+ } else {
+ //async saving data in js model
+ someModel.asyncSaveMethod({
+ ...,
+ success: function(){
+ d.resolve();
+ }
+ });
+ return d.promise();
+ }
+ }
+ **/
+ url:null,
+ /**
+ Additional params for submit. If defined as object - it is **appended** to original ajax data (pk, name and value).
+ If defined as function - returned object **overwrites** original ajax data.
+ @example
+ params: function(params) {
+ //originally params contain pk, name and value
+ params.a = 1;
+ return params;
+ }
+
+ @property params
+ @type object|function
+ @default null
+ **/
+ params:null,
+ /**
+ Name of field. Will be submitted on server. Can be taken from id attribute
+
+ @property name
+ @type string
+ @default null
+ **/
+ name: null,
+ /**
+ Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. {id: 1, lang: 'en'}.
+ Can be calculated dynamically via function.
+
+ @property pk
+ @type string|object|function
+ @default null
+ **/
+ pk: null,
+ /**
+ Initial value. If not defined - will be taken from element's content.
+ For __select__ type should be defined (as it is ID of shown text).
+
+ @property value
+ @type string|object
+ @default null
+ **/
+ value: null,
+ /**
+ Value that will be displayed in input if original field value is empty (`null|undefined|''`).
+
+ @property defaultValue
+ @type string|object
+ @default null
+ @since 1.4.6
+ **/
+ defaultValue: null,
+ /**
+ Strategy for sending data on server. Can be `auto|always|never`.
+ When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
+
+ @property send
+ @type string
+ @default 'auto'
+ **/
+ send: 'auto',
+ /**
+ Function for client-side validation. If returns string - means validation not passed and string showed as error.
+ Since 1.5.1 you can modify submitted value by returning object from `validate`:
+ `{newValue: '...'}` or `{newValue: '...', msg: '...'}`
+
+ @property validate
+ @type function
+ @default null
+ @example
+ validate: function(value) {
+ if($.trim(value) == '') {
+ return 'This field is required';
+ }
+ }
+ **/
+ validate: null,
+ /**
+ Success callback. Called when value successfully sent on server and **response status = 200**.
+ Usefull to work with json response. For example, if your backend response can be {success: true}
+ or {success: false, msg: "server error"} you can check it inside this callback.
+ If it returns **string** - means error occured and string is shown as error message.
+ If it returns **object like** {newValue: <something>} - it overwrites value, submitted by user.
+ Otherwise newValue simply rendered into element.
+
+ @property success
+ @type function
+ @default null
+ @example
+ success: function(response, newValue) {
+ if(!response.success) return response.msg;
+ }
+ **/
+ success: null,
+ /**
+ Error callback. Called when request failed (response status != 200).
+ Usefull when you want to parse error response and display a custom message.
+ Must return **string** - the message to be displayed in the error block.
+
+ @property error
+ @type function
+ @default null
+ @since 1.4.4
+ @example
+ error: function(response, newValue) {
+ if(response.status === 500) {
+ return 'Service unavailable. Please try later.';
+ } else {
+ return response.responseText;
+ }
+ }
+ **/
+ error: null,
+ /**
+ Additional options for submit ajax request.
+ List of values: http://api.jquery.com/jQuery.ajax
+
+ @property ajaxOptions
+ @type object
+ @default null
+ @since 1.1.1
+ @example
+ ajaxOptions: {
+ type: 'put',
+ dataType: 'json'
+ }
+ **/
+ ajaxOptions: null,
+ /**
+ Where to show buttons: left(true)|bottom|false
+ Form without buttons is auto-submitted.
+
+ @property showbuttons
+ @type boolean|string
+ @default true
+ @since 1.1.1
+ **/
+ showbuttons: true,
+ /**
+ Scope for callback methods (success, validate).
+ If null means editableform instance itself.
+
+ @property scope
+ @type DOMElement|object
+ @default null
+ @since 1.2.0
+ @private
+ **/
+ scope: null,
+ /**
+ Whether to save or cancel value when it was not changed but form was submitted
+
+ @property savenochange
+ @type boolean
+ @default false
+ @since 1.2.0
+ **/
+ savenochange: false
+ };
+
+ /*
+ Note: following params could redefined in engine: bootstrap or jqueryui:
+ Classes 'control-group' and 'editable-error-block' must always present!
+ */
+ $.fn.editableform.template = '';
+
+ //loading div
+ $.fn.editableform.loading = '';
+
+ //buttons
+ $.fn.editableform.buttons = ''+
+ '';
+
+ //error class attached to control-group
+ $.fn.editableform.errorGroupClass = null;
+
+ //error class attached to editable-error-block
+ $.fn.editableform.errorBlockClass = 'editable-error';
+
+ //engine
+ $.fn.editableform.engine = 'jquery';
+}(window.jQuery));
+
+/**
+* EditableForm utilites
+*/
+(function ($) {
+ "use strict";
+
+ //utils
+ $.fn.editableutils = {
+ /**
+ * classic JS inheritance function
+ */
+ inherit: function (Child, Parent) {
+ var F = function() { };
+ F.prototype = Parent.prototype;
+ Child.prototype = new F();
+ Child.prototype.constructor = Child;
+ Child.superclass = Parent.prototype;
+ },
+
+ /**
+ * set caret position in input
+ * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
+ */
+ setCursorPosition: function(elem, pos) {
+ if (elem.setSelectionRange) {
+ elem.setSelectionRange(pos, pos);
+ } else if (elem.createTextRange) {
+ var range = elem.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', pos);
+ range.moveStart('character', pos);
+ range.select();
+ }
+ },
+
+ /**
+ * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
+ * That allows such code as:
+ * safe = true --> means no exception will be thrown
+ * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
+ */
+ tryParseJson: function(s, safe) {
+ if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
+ if (safe) {
+ try {
+ /*jslint evil: true*/
+ s = (new Function('return ' + s))();
+ /*jslint evil: false*/
+ } catch (e) {} finally {
+ return s;
+ }
+ } else {
+ /*jslint evil: true*/
+ s = (new Function('return ' + s))();
+ /*jslint evil: false*/
+ }
+ }
+ return s;
+ },
+
+ /**
+ * slice object by specified keys
+ */
+ sliceObj: function(obj, keys, caseSensitive /* default: false */) {
+ var key, keyLower, newObj = {};
+
+ if (!$.isArray(keys) || !keys.length) {
+ return newObj;
+ }
+
+ for (var i = 0; i < keys.length; i++) {
+ key = keys[i];
+ if (obj.hasOwnProperty(key)) {
+ newObj[key] = obj[key];
+ }
+
+ if(caseSensitive === true) {
+ continue;
+ }
+
+ //when getting data-* attributes via $.data() it's converted to lowercase.
+ //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
+ //workaround is code below.
+ keyLower = key.toLowerCase();
+ if (obj.hasOwnProperty(keyLower)) {
+ newObj[key] = obj[keyLower];
+ }
+ }
+
+ return newObj;
+ },
+
+ /*
+ exclude complex objects from $.data() before pass to config
+ */
+ getConfigData: function($element) {
+ var data = {};
+ $.each($element.data(), function(k, v) {
+ if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
+ data[k] = v;
+ }
+ });
+ return data;
+ },
+
+ /*
+ returns keys of object
+ */
+ objectKeys: function(o) {
+ if (Object.keys) {
+ return Object.keys(o);
+ } else {
+ if (o !== Object(o)) {
+ throw new TypeError('Object.keys called on a non-object');
+ }
+ var k=[], p;
+ for (p in o) {
+ if (Object.prototype.hasOwnProperty.call(o,p)) {
+ k.push(p);
+ }
+ }
+ return k;
+ }
+
+ },
+
+ /**
+ method to escape html.
+ **/
+ escape: function(str) {
+ return $('$().editable(). You should subscribe on it's events (save / cancel) to get profit of it.save|cancel|onblur|nochange|undefined (=manual)
+ **/
+ hide: function(reason) {
+ if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
+ return;
+ }
+
+ //if form is saving value, schedule hide
+ if(this.$form.data('editableform').isSaving) {
+ this.delayedHide = {reason: reason};
+ return;
+ } else {
+ this.delayedHide = false;
+ }
+
+ this.$element.removeClass('editable-open');
+ this.innerHide();
+
+ /**
+ Fired when container was hidden. It occurs on both save or cancel.
+ **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
+ The workaround is to check `arguments.length` that is always `2` for x-editable.
+
+ @event hidden
+ @param {object} event event object
+ @param {string} reason Reason caused hiding. Can be save|cancel|onblur|nochange|manual
+ @example
+ $('#username').on('hidden', function(e, reason) {
+ if(reason === 'save' || reason === 'cancel') {
+ //auto-open next editable
+ $(this).closest('tr').next().find('.editable').editable('show');
+ }
+ });
+ **/
+ this.$element.triggerHandler('hidden', reason || 'manual');
+ },
+
+ /* internal show method. To be overwritten in child classes */
+ innerShow: function () {
+
+ },
+
+ /* internal hide method. To be overwritten in child classes */
+ innerHide: function () {
+
+ },
+
+ /**
+ Toggles container visibility (show / hide)
+ @method toggle()
+ @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
+ **/
+ toggle: function(closeAll) {
+ if(this.container() && this.tip() && this.tip().is(':visible')) {
+ this.hide();
+ } else {
+ this.show(closeAll);
+ }
+ },
+
+ /*
+ Updates the position of container when content changed.
+ @method setPosition()
+ */
+ setPosition: function() {
+ //tbd in child class
+ },
+
+ save: function(e, params) {
+ /**
+ Fired when new value was submitted. You can use $(this).data('editableContainer') inside handler to access to editableContainer instance
+
+ @event save
+ @param {Object} event event object
+ @param {Object} params additional params
+ @param {mixed} params.newValue submitted value
+ @param {Object} params.response ajax response
+ @example
+ $('#username').on('save', function(e, params) {
+ //assuming server response: '{success: true}'
+ var pk = $(this).data('editableContainer').options.pk;
+ if(params.response && params.response.success) {
+ alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
+ } else {
+ alert('error!');
+ }
+ });
+ **/
+ this.$element.triggerHandler('save', params);
+
+ //hide must be after trigger, as saving value may require methods of plugin, applied to input
+ this.hide('save');
+ },
+
+ /**
+ Sets new option
+
+ @method option(key, value)
+ @param {string} key
+ @param {mixed} value
+ **/
+ option: function(key, value) {
+ this.options[key] = value;
+ if(key in this.containerOptions) {
+ this.containerOptions[key] = value;
+ this.setContainerOption(key, value);
+ } else {
+ this.formOptions[key] = value;
+ if(this.$form) {
+ this.$form.editableform('option', key, value);
+ }
+ }
+ },
+
+ setContainerOption: function(key, value) {
+ this.call('option', key, value);
+ },
+
+ /**
+ Destroys the container instance
+ @method destroy()
+ **/
+ destroy: function() {
+ this.hide();
+ this.innerDestroy();
+ this.$element.off('destroyed');
+ this.$element.removeData('editableContainer');
+ },
+
+ /* to be overwritten in child classes */
+ innerDestroy: function() {
+
+ },
+
+ /*
+ Closes other containers except one related to passed element.
+ Other containers can be cancelled or submitted (depends on onblur option)
+ */
+ closeOthers: function(element) {
+ $('.editable-open').each(function(i, el){
+ //do nothing with passed element and it's children
+ if(el === element || $(el).find(element).length) {
+ return;
+ }
+
+ //otherwise cancel or submit all open containers
+ var $el = $(el),
+ ec = $el.data('editableContainer');
+
+ if(!ec) {
+ return;
+ }
+
+ if(ec.options.onblur === 'cancel') {
+ $el.data('editableContainer').hide('onblur');
+ } else if(ec.options.onblur === 'submit') {
+ $el.data('editableContainer').tip().find('form').submit();
+ }
+ });
+
+ },
+
+ /**
+ Activates input of visible container (e.g. set focus)
+ @method activate()
+ **/
+ activate: function() {
+ if(this.tip && this.tip().is(':visible') && this.$form) {
+ this.$form.data('editableform').input.activate();
+ }
+ }
+
+ };
+
+ /**
+ jQuery method to initialize editableContainer.
+
+ @method $().editableContainer(options)
+ @params {Object} options
+ @example
+ $('#edit').editableContainer({
+ type: 'text',
+ url: '/post',
+ pk: 1,
+ value: 'hello'
+ });
+ **/
+ $.fn.editableContainer = function (option) {
+ var args = arguments;
+ return this.each(function () {
+ var $this = $(this),
+ dataKey = 'editableContainer',
+ data = $this.data(dataKey),
+ options = typeof option === 'object' && option,
+ Constructor = (options.mode === 'inline') ? Inline : Popup;
+
+ if (!data) {
+ $this.data(dataKey, (data = new Constructor(this, options)));
+ }
+
+ if (typeof option === 'string') { //call method
+ data[option].apply(data, Array.prototype.slice.call(args, 1));
+ }
+ });
+ };
+
+ //store constructors
+ $.fn.editableContainer.Popup = Popup;
+ $.fn.editableContainer.Inline = Inline;
+
+ //defaults
+ $.fn.editableContainer.defaults = {
+ /**
+ Initial value of form input
+
+ @property value
+ @type mixed
+ @default null
+ @private
+ **/
+ value: null,
+ /**
+ Placement of container relative to element. Can be top|right|bottom|left. Not used for inline container.
+
+ @property placement
+ @type string
+ @default 'top'
+ **/
+ placement: 'top',
+ /**
+ Whether to hide container on save/cancel.
+
+ @property autohide
+ @type boolean
+ @default true
+ @private
+ **/
+ autohide: true,
+ /**
+ Action when user clicks outside the container. Can be cancel|submit|ignore.
+ Setting ignore allows to have several containers open.
+
+ @property onblur
+ @type string
+ @default 'cancel'
+ @since 1.1.1
+ **/
+ onblur: 'cancel',
+
+ /**
+ Animation speed (inline mode only)
+ @property anim
+ @type string
+ @default false
+ **/
+ anim: false,
+
+ /**
+ Mode of editable, can be `popup` or `inline`
+
+ @property mode
+ @type string
+ @default 'popup'
+ @since 1.4.0
+ **/
+ mode: 'popup'
+ };
+
+ /*
+ * workaround to have 'destroyed' event to destroy popover when element is destroyed
+ * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
+ */
+ jQuery.event.special.destroyed = {
+ remove: function(o) {
+ if (o.handler) {
+ o.handler();
+ }
+ }
+ };
+
+}(window.jQuery));
+
+/**
+* Editable Inline
+* ---------------------
+*/
+(function ($) {
+ "use strict";
+
+ //copy prototype from EditableContainer
+ //extend methods
+ $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
+ containerName: 'editableform',
+ innerCss: '.editable-inline',
+ containerClass: 'editable-container editable-inline', //css class applied to container element
+
+ initContainer: function(){
+ //container is element
+ this.$tip = $('');
+
+ //convert anim to miliseconds (int)
+ if(!this.options.anim) {
+ this.options.anim = 0;
+ }
+ },
+
+ splitOptions: function() {
+ //all options are passed to form
+ this.containerOptions = {};
+ this.formOptions = this.options;
+ },
+
+ tip: function() {
+ return this.$tip;
+ },
+
+ innerShow: function () {
+ this.$element.hide();
+ this.tip().insertAfter(this.$element).show();
+ },
+
+ innerHide: function () {
+ this.$tip.hide(this.options.anim, $.proxy(function() {
+ this.$element.show();
+ this.innerDestroy();
+ }, this));
+ },
+
+ innerDestroy: function() {
+ if(this.tip()) {
+ this.tip().empty().remove();
+ }
+ }
+ });
+
+}(window.jQuery));
+/**
+Makes editable any HTML element on the page. Applied as jQuery method.
+
+@class editable
+@uses editableContainer
+**/
+(function ($) {
+ "use strict";
+
+ var Editable = function (element, options) {
+ this.$element = $(element);
+ //data-* has more priority over js options: because dynamically created elements may change data-*
+ this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
+ if(this.options.selector) {
+ this.initLive();
+ } else {
+ this.init();
+ }
+
+ //check for transition support
+ if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
+ this.options.highlight = false;
+ }
+ };
+
+ Editable.prototype = {
+ constructor: Editable,
+ init: function () {
+ var isValueByText = false,
+ doAutotext, finalize;
+
+ //name
+ this.options.name = this.options.name || this.$element.attr('id');
+
+ //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
+ //also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
+ this.options.scope = this.$element[0];
+ this.input = $.fn.editableutils.createInput(this.options);
+ if(!this.input) {
+ return;
+ }
+
+ //set value from settings or by element's text
+ if (this.options.value === undefined || this.options.value === null) {
+ this.value = this.input.html2value($.trim(this.$element.html()));
+ isValueByText = true;
+ } else {
+ /*
+ value can be string when received from 'data-value' attribute
+ for complext objects value can be set as json string in data-value attribute,
+ e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
+ */
+ this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
+ if(typeof this.options.value === 'string') {
+ this.value = this.input.str2value(this.options.value);
+ } else {
+ this.value = this.options.value;
+ }
+ }
+
+ //add 'editable' class to every editable element
+ this.$element.addClass('editable');
+
+ //specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
+ if(this.input.type === 'textarea') {
+ this.$element.addClass('editable-pre-wrapped');
+ }
+
+ //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
+ if(this.options.toggle !== 'manual') {
+ this.$element.addClass('editable-click');
+ this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
+ //prevent following link if editable enabled
+ if(!this.options.disabled) {
+ e.preventDefault();
+ }
+
+ //stop propagation not required because in document click handler it checks event target
+ //e.stopPropagation();
+
+ if(this.options.toggle === 'mouseenter') {
+ //for hover only show container
+ this.show();
+ } else {
+ //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
+ var closeAll = (this.options.toggle !== 'click');
+ this.toggle(closeAll);
+ }
+ }, this));
+ } else {
+ this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
+ }
+
+ //if display is function it's far more convinient to have autotext = always to render correctly on init
+ //see https://github.com/vitalets/x-editable-yii/issues/34
+ if(typeof this.options.display === 'function') {
+ this.options.autotext = 'always';
+ }
+
+ //check conditions for autotext:
+ switch(this.options.autotext) {
+ case 'always':
+ doAutotext = true;
+ break;
+ case 'auto':
+ //if element text is empty and value is defined and value not generated by text --> run autotext
+ doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
+ break;
+ default:
+ doAutotext = false;
+ }
+
+ //depending on autotext run render() or just finilize init
+ $.when(doAutotext ? this.render() : true).then($.proxy(function() {
+ if(this.options.disabled) {
+ this.disable();
+ } else {
+ this.enable();
+ }
+ /**
+ Fired when element was initialized by `$().editable()` method.
+ Please note that you should setup `init` handler **before** applying `editable`.
+
+ @event init
+ @param {Object} event event object
+ @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
+ @since 1.2.0
+ @example
+ $('#username').on('init', function(e, editable) {
+ alert('initialized ' + editable.options.name);
+ });
+ $('#username').editable();
+ **/
+ this.$element.triggerHandler('init', this);
+ }, this));
+ },
+
+ /*
+ Initializes parent element for live editables
+ */
+ initLive: function() {
+ //store selector
+ var selector = this.options.selector;
+ //modify options for child elements
+ this.options.selector = false;
+ this.options.autotext = 'never';
+ //listen toggle events
+ this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
+ var $target = $(e.target);
+ if(!$target.data('editable')) {
+ //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
+ //see https://github.com/vitalets/x-editable/issues/137
+ if($target.hasClass(this.options.emptyclass)) {
+ $target.empty();
+ }
+ $target.editable(this.options).trigger(e);
+ }
+ }, this));
+ },
+
+ /*
+ Renders value into element's text.
+ Can call custom display method from options.
+ Can return deferred object.
+ @method render()
+ @param {mixed} response server response (if exist) to pass into display function
+ */
+ render: function(response) {
+ //do not display anything
+ if(this.options.display === false) {
+ return;
+ }
+
+ //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
+ if(this.input.value2htmlFinal) {
+ return this.input.value2html(this.value, this.$element[0], this.options.display, response);
+ //if display method defined --> use it
+ } else if(typeof this.options.display === 'function') {
+ return this.options.display.call(this.$element[0], this.value, response);
+ //else use input's original value2html() method
+ } else {
+ return this.input.value2html(this.value, this.$element[0]);
+ }
+ },
+
+ /**
+ Enables editable
+ @method enable()
+ **/
+ enable: function() {
+ this.options.disabled = false;
+ this.$element.removeClass('editable-disabled');
+ this.handleEmpty(this.isEmpty);
+ if(this.options.toggle !== 'manual') {
+ if(this.$element.attr('tabindex') === '-1') {
+ this.$element.removeAttr('tabindex');
+ }
+ }
+ },
+
+ /**
+ Disables editable
+ @method disable()
+ **/
+ disable: function() {
+ this.options.disabled = true;
+ this.hide();
+ this.$element.addClass('editable-disabled');
+ this.handleEmpty(this.isEmpty);
+ //do not stop focus on this element
+ this.$element.attr('tabindex', -1);
+ },
+
+ /**
+ Toggles enabled / disabled state of editable element
+ @method toggleDisabled()
+ **/
+ toggleDisabled: function() {
+ if(this.options.disabled) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ },
+
+ /**
+ Sets new option
+
+ @method option(key, value)
+ @param {string|object} key option name or object with several options
+ @param {mixed} value option new value
+ @example
+ $('.editable').editable('option', 'pk', 2);
+ **/
+ option: function(key, value) {
+ //set option(s) by object
+ if(key && typeof key === 'object') {
+ $.each(key, $.proxy(function(k, v){
+ this.option($.trim(k), v);
+ }, this));
+ return;
+ }
+
+ //set option by string
+ this.options[key] = value;
+
+ //disabled
+ if(key === 'disabled') {
+ return value ? this.disable() : this.enable();
+ }
+
+ //value
+ if(key === 'value') {
+ this.setValue(value);
+ }
+
+ //transfer new option to container!
+ if(this.container) {
+ this.container.option(key, value);
+ }
+
+ //pass option to input directly (as it points to the same in form)
+ if(this.input.option) {
+ this.input.option(key, value);
+ }
+
+ },
+
+ /*
+ * set emptytext if element is empty
+ */
+ handleEmpty: function (isEmpty) {
+ //do not handle empty if we do not display anything
+ if(this.options.display === false) {
+ return;
+ }
+
+ /*
+ isEmpty may be set directly as param of method.
+ It is required when we enable/disable field and can't rely on content
+ as node content is text: "Empty" that is not empty %)
+ */
+ if(isEmpty !== undefined) {
+ this.isEmpty = isEmpty;
+ } else {
+ //detect empty
+ //for some inputs we need more smart check
+ //e.g. wysihtml5 may have $(this).data('editable') to access to editable instance
+
+ @event save
+ @param {Object} event event object
+ @param {Object} params additional params
+ @param {mixed} params.newValue submitted value
+ @param {Object} params.response ajax response
+ @example
+ $('#username').on('save', function(e, params) {
+ alert('Saved value: ' + params.newValue);
+ });
+ **/
+ //event itself is triggered by editableContainer. Description here is only for documentation
+ },
+
+ validate: function () {
+ if (typeof this.options.validate === 'function') {
+ return this.options.validate.call(this, this.value);
+ }
+ },
+
+ /**
+ Sets new value of editable
+ @method setValue(value, convertStr)
+ @param {mixed} value new value
+ @param {boolean} convertStr whether to convert value from string to internal format
+ **/
+ setValue: function(value, convertStr, response) {
+ if(convertStr) {
+ this.value = this.input.str2value(value);
+ } else {
+ this.value = value;
+ }
+ if(this.container) {
+ this.container.option('value', this.value);
+ }
+ $.when(this.render(response))
+ .then($.proxy(function() {
+ this.handleEmpty();
+ }, this));
+ },
+
+ /**
+ Activates input of visible container (e.g. set focus)
+ @method activate()
+ **/
+ activate: function() {
+ if(this.container) {
+ this.container.activate();
+ }
+ },
+
+ /**
+ Removes editable feature from element
+ @method destroy()
+ **/
+ destroy: function() {
+ this.disable();
+
+ if(this.container) {
+ this.container.destroy();
+ }
+
+ this.input.destroy();
+
+ if(this.options.toggle !== 'manual') {
+ this.$element.removeClass('editable-click');
+ this.$element.off(this.options.toggle + '.editable');
+ }
+
+ this.$element.off("save.internal");
+
+ this.$element.removeClass('editable editable-open editable-disabled');
+ this.$element.removeData('editable');
+ }
+ };
+
+ /* EDITABLE PLUGIN DEFINITION
+ * ======================= */
+
+ /**
+ jQuery method to initialize editable element.
+
+ @method $().editable(options)
+ @params {Object} options
+ @example
+ $('#username').editable({
+ type: 'text',
+ url: '/post',
+ pk: 1
+ });
+ **/
+ $.fn.editable = function (option) {
+ //special API methods returning non-jquery object
+ var result = {}, args = arguments, datakey = 'editable';
+ switch (option) {
+ /**
+ Runs client-side validation for all matched editables
+
+ @method validate()
+ @returns {Object} validation errors map
+ @example
+ $('#username, #fullname').editable('validate');
+ // possible result:
+ {
+ username: "username is required",
+ fullname: "fullname should be minimum 3 letters length"
+ }
+ **/
+ case 'validate':
+ this.each(function () {
+ var $this = $(this), data = $this.data(datakey), error;
+ if (data && (error = data.validate())) {
+ result[data.options.name] = error;
+ }
+ });
+ return result;
+
+ /**
+ Returns current values of editable elements.
+ Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
+ If value of some editable is `null` or `undefined` it is excluded from result object.
+ When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
+
+ @method getValue()
+ @param {bool} isSingle whether to return just value of single element
+ @returns {Object} object of element names and values
+ @example
+ $('#username, #fullname').editable('getValue');
+ //result:
+ {
+ username: "superuser",
+ fullname: "John"
+ }
+ //isSingle = true
+ $('#username').editable('getValue', true);
+ //result "superuser"
+ **/
+ case 'getValue':
+ if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
+ result = this.eq(0).data(datakey).value;
+ } else {
+ this.each(function () {
+ var $this = $(this), data = $this.data(datakey);
+ if (data && data.value !== undefined && data.value !== null) {
+ result[data.options.name] = data.input.value2submit(data.value);
+ }
+ });
+ }
+ return result;
+
+ /**
+ This method collects values from several editable elements and submit them all to server.
+ Internally it runs client-side validation for all fields and submits only in case of success.
+ See creating new records for details.
+ Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
+ `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`.
+
+ @method submit(options)
+ @param {object} options
+ @param {object} options.url url to submit data
+ @param {object} options.data additional data to submit
+ @param {object} options.ajaxOptions additional ajax options
+ @param {function} options.error(obj) error handler
+ @param {function} options.success(obj,config) success handler
+ @returns {Object} jQuery object
+ **/
+ case 'submit': //collects value, validate and submit to server for creating new record
+ var config = arguments[1] || {},
+ $elems = this,
+ errors = this.editable('validate');
+
+ // validation ok
+ if($.isEmptyObject(errors)) {
+ var ajaxOptions = {};
+
+ // for single element use url, success etc from options
+ if($elems.length === 1) {
+ var editable = $elems.data('editable');
+ //standard params
+ var params = {
+ name: editable.options.name || '',
+ value: editable.input.value2submit(editable.value),
+ pk: (typeof editable.options.pk === 'function') ?
+ editable.options.pk.call(editable.options.scope) :
+ editable.options.pk
+ };
+
+ //additional params
+ if(typeof editable.options.params === 'function') {
+ params = editable.options.params.call(editable.options.scope, params);
+ } else {
+ //try parse json in single quotes (from data-params attribute)
+ editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);
+ $.extend(params, editable.options.params);
+ }
+
+ ajaxOptions = {
+ url: editable.options.url,
+ data: params,
+ type: 'POST'
+ };
+
+ // use success / error from options
+ config.success = config.success || editable.options.success;
+ config.error = config.error || editable.options.error;
+
+ // multiple elements
+ } else {
+ var values = this.editable('getValue');
+
+ ajaxOptions = {
+ url: config.url,
+ data: values,
+ type: 'POST'
+ };
+ }
+
+ // ajax success callabck (response 200 OK)
+ ajaxOptions.success = typeof config.success === 'function' ? function(response) {
+ config.success.call($elems, response, config);
+ } : $.noop;
+
+ // ajax error callabck
+ ajaxOptions.error = typeof config.error === 'function' ? function() {
+ config.error.apply($elems, arguments);
+ } : $.noop;
+
+ // extend ajaxOptions
+ if(config.ajaxOptions) {
+ $.extend(ajaxOptions, config.ajaxOptions);
+ }
+
+ // extra data
+ if(config.data) {
+ $.extend(ajaxOptions.data, config.data);
+ }
+
+ // perform ajax request
+ $.ajax(ajaxOptions);
+ } else { //client-side validation error
+ if(typeof config.error === 'function') {
+ config.error.call($elems, errors);
+ }
+ }
+ return this;
+ }
+
+ //return jquery object
+ return this.each(function () {
+ var $this = $(this),
+ data = $this.data(datakey),
+ options = typeof option === 'object' && option;
+
+ //for delegated targets do not store `editable` object for element
+ //it's allows several different selectors.
+ //see: https://github.com/vitalets/x-editable/issues/312
+ if(options && options.selector) {
+ data = new Editable(this, options);
+ return;
+ }
+
+ if (!data) {
+ $this.data(datakey, (data = new Editable(this, options)));
+ }
+
+ if (typeof option === 'string') { //call method
+ data[option].apply(data, Array.prototype.slice.call(args, 1));
+ }
+ });
+ };
+
+
+ $.fn.editable.defaults = {
+ /**
+ Type of input. Can be text|textarea|select|date|checklist and more
+
+ @property type
+ @type string
+ @default 'text'
+ **/
+ type: 'text',
+ /**
+ Sets disabled state of editable
+
+ @property disabled
+ @type boolean
+ @default false
+ **/
+ disabled: false,
+ /**
+ How to toggle editable. Can be click|dblclick|mouseenter|manual.
+ When set to manual you should manually call show/hide methods of editable.
+ **Note**: if you call show or toggle inside **click** handler of some DOM element,
+ you need to apply e.stopPropagation() because containers are being closed on any click on document.
+
+ @example
+ $('#edit-button').click(function(e) {
+ e.stopPropagation();
+ $('#username').editable('toggle');
+ });
+
+ @property toggle
+ @type string
+ @default 'click'
+ **/
+ toggle: 'click',
+ /**
+ Text shown when element is empty.
+
+ @property emptytext
+ @type string
+ @default 'Empty'
+ **/
+ emptytext: 'Empty',
+ /**
+ Allows to automatically set element's text based on it's value. Can be auto|always|never. Useful for select and date.
+ For example, if dropdown list is {1: 'a', 2: 'b'} and element's value set to 1, it's html will be automatically set to 'a'.
+ auto - text will be automatically set only if element is empty.
+ always|never - always(never) try to set element's text.
+
+ @property autotext
+ @type string
+ @default 'auto'
+ **/
+ autotext: 'auto',
+ /**
+ Initial value of input. If not set, taken from element's text.
+ Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).
+ For example, to display currency sign:
+ @example
+
+
+
+ @property value
+ @type mixed
+ @default element's text
+ **/
+ value: null,
+ /**
+ Callback to perform custom displaying of value in element's text.
+ If `null`, default input's display used.
+ If `false`, no displaying methods will be called, element's text will never change.
+ Runs under element's scope.
+ _**Parameters:**_
+
+ * `value` current value to be displayed
+ * `response` server response (if display called after ajax submit), since 1.4.0
+
+ For _inputs with source_ (select, checklist) parameters are different:
+
+ * `value` current value to be displayed
+ * `sourceData` array of items for current input (e.g. dropdown items)
+ * `response` server response (if display called after ajax submit), since 1.4.0
+
+ To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
+
+ @property display
+ @type function|boolean
+ @default null
+ @since 1.2.0
+ @example
+ display: function(value, sourceData) {
+ //display checklist as comma-separated values
+ var html = [],
+ checked = $.fn.editableutils.itemsByValue(value, sourceData);
+
+ if(checked.length) {
+ $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
+ $(this).html(html.join(', '));
+ } else {
+ $(this).empty();
+ }
+ }
+ **/
+ display: null,
+ /**
+ Css class applied when editable text is empty.
+
+ @property emptyclass
+ @type string
+ @since 1.4.1
+ @default editable-empty
+ **/
+ emptyclass: 'editable-empty',
+ /**
+ Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
+ You may set it to `null` if you work with editables locally and submit them together.
+
+ @property unsavedclass
+ @type string
+ @since 1.4.1
+ @default editable-unsaved
+ **/
+ unsavedclass: 'editable-unsaved',
+ /**
+ If selector is provided, editable will be delegated to the specified targets.
+ Usefull for dynamically generated DOM elements.
+ **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options,
+ as they actually become editable only after first click.
+ You should manually set class `editable-click` to these elements.
+ Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
+
+ @property selector
+ @type string
+ @since 1.4.1
+ @default null
+ @example
+
+
+
+ **/
+ selector: null,
+ /**
+ Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
+
+ @property highlight
+ @type string|boolean
+ @since 1.4.5
+ @default #FFFF80
+ **/
+ highlight: '#FFFF80'
+ };
+
+}(window.jQuery));
+
+/**
+AbstractInput - base class for all editable inputs.
+It defines interface to be implemented by any input type.
+To create your own input you can inherit from this class.
+
+@class abstractinput
+**/
+(function ($) {
+ "use strict";
+
+ //types
+ $.fn.editabletypes = {};
+
+ var AbstractInput = function () { };
+
+ AbstractInput.prototype = {
+ /**
+ Initializes input
+
+ @method init()
+ **/
+ init: function(type, options, defaults) {
+ this.type = type;
+ this.options = $.extend({}, defaults, options);
+ },
+
+ /*
+ this method called before render to init $tpl that is inserted in DOM
+ */
+ prerender: function() {
+ this.$tpl = $(this.options.tpl); //whole tpl as jquery object
+ this.$input = this.$tpl; //control itself, can be changed in render method
+ this.$clear = null; //clear button
+ this.error = null; //error message, if input cannot be rendered
+ },
+
+ /**
+ Renders input from tpl. Can return jQuery deferred object.
+ Can be overwritten in child objects
+
+ @method render()
+ **/
+ render: function() {
+
+ },
+
+ /**
+ Sets element's html by value.
+
+ @method value2html(value, element)
+ @param {mixed} value
+ @param {DOMElement} element
+ **/
+ value2html: function(value, element) {
+ $(element)[this.options.escape ? 'text' : 'html']($.trim(value));
+ },
+
+ /**
+ Converts element's html to value
+
+ @method html2value(html)
+ @param {string} html
+ @returns {mixed}
+ **/
+ html2value: function(html) {
+ return $('true and source is **string url** - results will be cached for fields with the same source.
+ Usefull for editable column in grid to prevent extra requests.
+
+ @property sourceCache
+ @type boolean
+ @default true
+ @since 1.2.0
+ **/
+ sourceCache: true,
+ /**
+ Additional ajax options to be used in $.ajax() when loading list from server.
+ Useful to send extra parameters (`data` key) or change request method (`type` key).
+
+ @property sourceOptions
+ @type object|function
+ @default null
+ @since 1.5.0
+ **/
+ sourceOptions: null
+ });
+
+ $.fn.editabletypes.list = List;
+
+}(window.jQuery));
+
+/**
+Text input
+
+@class text
+@extends abstractinput
+@final
+@example
+awesome
+
+**/
+(function ($) {
+ "use strict";
+
+ var Text = function (options) {
+ this.init('text', options, Text.defaults);
+ };
+
+ $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
+
+ $.extend(Text.prototype, {
+ render: function() {
+ this.renderClear();
+ this.setClass();
+ this.setAttr('placeholder');
+ },
+
+ activate: function() {
+ if(this.$input.is(':visible')) {
+ this.$input.focus();
+ $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
+ if(this.toggleClear) {
+ this.toggleClear();
+ }
+ }
+ },
+
+ //render clear button
+ renderClear: function() {
+ if (this.options.clear) {
+ this.$clear = $('');
+ this.$input.after(this.$clear)
+ .css('padding-right', 24)
+ .keyup($.proxy(function(e) {
+ //arrows, enter, tab, etc
+ if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
+ return;
+ }
+
+ clearTimeout(this.t);
+ var that = this;
+ this.t = setTimeout(function() {
+ that.toggleClear(e);
+ }, 100);
+
+ }, this))
+ .parent().css('position', 'relative');
+
+ this.$clear.click($.proxy(this.clear, this));
+ }
+ },
+
+ postrender: function() {
+ /*
+ //now `clear` is positioned via css
+ if(this.$clear) {
+ //can position clear button only here, when form is shown and height can be calculated
+// var h = this.$input.outerHeight(true) || 20,
+ var h = this.$clear.parent().height(),
+ delta = (h - this.$clear.height()) / 2;
+
+ //this.$clear.css({bottom: delta, right: delta});
+ }
+ */
+ },
+
+ //show / hide clear button
+ toggleClear: function(e) {
+ if(!this.$clear) {
+ return;
+ }
+
+ var len = this.$input.val().length,
+ visible = this.$clear.is(':visible');
+
+ if(len && !visible) {
+ this.$clear.show();
+ }
+
+ if(!len && visible) {
+ this.$clear.hide();
+ }
+ },
+
+ clear: function() {
+ this.$clear.hide();
+ this.$input.val('').focus();
+ }
+ });
+
+ Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+ /**
+ @property tpl
+ @default
+ **/
+ tpl: '',
+ /**
+ Placeholder attribute of input. Shown when input is empty.
+
+ @property placeholder
+ @type string
+ @default null
+ **/
+ placeholder: null,
+
+ /**
+ Whether to show `clear` button
+
+ @property clear
+ @type boolean
+ @default true
+ **/
+ clear: true
+ });
+
+ $.fn.editabletypes.text = Text;
+
+}(window.jQuery));
+
+/**
+Textarea input
+
+@class textarea
+@extends abstractinput
+@final
+@example
+awesome comment!
+
+**/
+(function ($) {
+ "use strict";
+
+ var Textarea = function (options) {
+ this.init('textarea', options, Textarea.defaults);
+ };
+
+ $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
+
+ $.extend(Textarea.prototype, {
+ render: function () {
+ this.setClass();
+ this.setAttr('placeholder');
+ this.setAttr('rows');
+
+ //ctrl + enter
+ this.$input.keydown(function (e) {
+ if (e.ctrlKey && e.which === 13) {
+ $(this).closest('form').submit();
+ }
+ });
+ },
+
+ //using `white-space: pre-wrap` solves \n <--> BR conversion very elegant!
+ /*
+ value2html: function(value, element) {
+ var html = '', lines;
+ if(value) {
+ lines = value.split("\n");
+ for (var i = 0; i < lines.length; i++) {
+ lines[i] = $('