Compare commits

...

3 Commits
master ... ajax

  1. 31
      .gitignore
  2. 1
      .ruby-version
  3. 5
      Gemfile
  4. 125
      Gemfile.lock
  5. 2
      app/assets/javascripts/application.js
  6. 194
      app/assets/javascripts/livres.js
  7. 1
      app/controllers/application_controller.rb
  8. 40
      app/controllers/livres_controller.rb
  9. 1
      app/models/livre.rb
  10. 2
      app/views/layouts/application.html.erb
  11. BIN
      app/views/livres/.index.html.erb.swp
  12. 30
      app/views/livres/index.html.erb
  13. 115
      bin/bundle
  14. 29
      bin/guard
  15. 2
      config/environments/development.rb

31
.gitignore vendored

@ -0,0 +1,31 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore uploaded files in development
/storage/*
!/storage/.keep
/node_modules
/yarn-error.log
/public/assets
.byebug_history
# Ignore master key for decrypting credentials and more.
/config/master.key

@ -0,0 +1 @@
2.6.3

@ -1,10 +1,8 @@
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.3'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.4', '>= 5.2.4.1'
gem 'rails'
gem 'puma', '~> 3.11'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
@ -36,6 +34,7 @@ group :development do
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'awesome_print'
gem 'guard-rails', require: false
gem 'guard-livereload', require: false
gem 'rack-livereload'

@ -1,56 +1,70 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (5.2.4.1)
actionpack (= 5.2.4.1)
actioncable (6.0.2.1)
actionpack (= 6.0.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.4.1)
actionpack (= 5.2.4.1)
actionview (= 5.2.4.1)
activejob (= 5.2.4.1)
actionmailbox (6.0.2.1)
actionpack (= 6.0.2.1)
activejob (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
mail (>= 2.7.1)
actionmailer (6.0.2.1)
actionpack (= 6.0.2.1)
actionview (= 6.0.2.1)
activejob (= 6.0.2.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.4.1)
actionview (= 5.2.4.1)
activesupport (= 5.2.4.1)
actionpack (6.0.2.1)
actionview (= 6.0.2.1)
activesupport (= 6.0.2.1)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.4.1)
activesupport (= 5.2.4.1)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.2.1)
actionpack (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
nokogiri (>= 1.8.5)
actionview (6.0.2.1)
activesupport (= 6.0.2.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.2.4.1)
activesupport (= 5.2.4.1)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.2.1)
activesupport (= 6.0.2.1)
globalid (>= 0.3.6)
activemodel (5.2.4.1)
activesupport (= 5.2.4.1)
activerecord (5.2.4.1)
activemodel (= 5.2.4.1)
activesupport (= 5.2.4.1)
arel (>= 9.0)
activestorage (5.2.4.1)
actionpack (= 5.2.4.1)
activerecord (= 5.2.4.1)
activemodel (6.0.2.1)
activesupport (= 6.0.2.1)
activerecord (6.0.2.1)
activemodel (= 6.0.2.1)
activesupport (= 6.0.2.1)
activestorage (6.0.2.1)
actionpack (= 6.0.2.1)
activejob (= 6.0.2.1)
activerecord (= 6.0.2.1)
marcel (~> 0.3.1)
activesupport (5.2.4.1)
activesupport (6.0.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
archive-zip (0.12.0)
io-like (~> 0.3.0)
arel (9.0.0)
autoprefixer-rails (9.7.4)
execjs
awesome_print (1.8.0)
bindex (0.8.1)
bootsnap (1.4.5)
bootsnap (1.4.6)
msgpack (~> 1.0)
bootstrap (4.3.1)
autoprefixer-rails (>= 9.1.0)
@ -143,7 +157,7 @@ GEM
multi_json (1.14.1)
nenv (0.3.0)
nio4r (2.5.2)
nokogiri (1.10.8)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
notiffany (0.1.3)
nenv (~> 0.1)
@ -153,45 +167,47 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.9.0)
public_suffix (4.0.3)
puma (3.12.2)
puma (3.12.4)
rack (2.2.2)
rack-livereload (0.3.17)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.4.1)
actioncable (= 5.2.4.1)
actionmailer (= 5.2.4.1)
actionpack (= 5.2.4.1)
actionview (= 5.2.4.1)
activejob (= 5.2.4.1)
activemodel (= 5.2.4.1)
activerecord (= 5.2.4.1)
activestorage (= 5.2.4.1)
activesupport (= 5.2.4.1)
rails (6.0.2.1)
actioncable (= 6.0.2.1)
actionmailbox (= 6.0.2.1)
actionmailer (= 6.0.2.1)
actionpack (= 6.0.2.1)
actiontext (= 6.0.2.1)
actionview (= 6.0.2.1)
activejob (= 6.0.2.1)
activemodel (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
bundler (>= 1.3.0)
railties (= 5.2.4.1)
railties (= 6.0.2.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
railties (5.2.4.1)
actionpack (= 5.2.4.1)
activesupport (= 5.2.4.1)
railties (6.0.2.1)
actionpack (= 6.0.2.1)
activesupport (= 6.0.2.1)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
thor (>= 0.20.3, < 2.0)
rake (13.0.1)
rb-fsevent (0.10.3)
rb-inotify (0.10.1)
ffi (~> 1.0)
regexp_parser (1.6.0)
regexp_parser (1.7.0)
rerun (0.13.0)
listen (~> 3.0)
ruby_dep (1.5.0)
rubyzip (2.2.0)
rubyzip (2.3.0)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@ -237,21 +253,23 @@ GEM
thread_safe (~> 0.1)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
web-console (3.7.0)
actionview (>= 5.0)
activemodel (>= 5.0)
web-console (4.0.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 5.0)
railties (>= 6.0.0)
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.3.0)
PLATFORMS
ruby
DEPENDENCIES
awesome_print
bootsnap (>= 1.1.0)
bootstrap (~> 4.3.1)
byebug
@ -267,7 +285,7 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2)
puma (~> 3.11)
rack-livereload
rails (~> 5.2.4, >= 5.2.4.1)
rails
rerun
sass-rails (~> 5.0)
selenium-webdriver
@ -279,8 +297,5 @@ DEPENDENCIES
uglifier (>= 1.3.0)
web-console (>= 3.3.0)
RUBY VERSION
ruby 2.6.3p62
BUNDLED WITH
1.17.2
2.1.4

@ -14,4 +14,4 @@
//= require activestorage
//= require jquery
//= require jquery.tabledit
//= require turbolinks
//= require_tree .

@ -1,47 +1,153 @@
$('#livres').Tabledit({
url: '/livres/tabledit',
editButton: true,
deleteButton: true,
buttons: {
edit: {
class: 'btn btn-dark',
html: '<span class="fa fa-edit"></span> Edit',
action: '/livres/tabledit'
},
delete: {
class: 'btn btn-dark',
html: '<span class="fa fa-trash"></span> Delete',
action: 'delete'
},
save: {
class: 'btn btn-success btn-sm',
html: '<span class="fa fa-save"></span> Save',
action: '/livres/update'
},
restore: {
class: 'btn btn-warning',
html: '<span class="fa fa-trash-restore"></span> Restore',
action: 'restore'
// supprime un livre basé sur son ID
function deleteLivre(id) {
const tr = document.querySelector('tr[data-id="' + id + '"]');
const attributes = {};
tr.querySelectorAll("td").forEach((td) => {
const name = td.dataset["name"];
const value = td.textContent;
attributes[name] = value;
});
let suppr = window.confirm('Êtes-vous sûr de vouloir supprimer le livre ' + attributes["titre"] + ' ?');
if (suppr) {
fetch ('/livres/' + id, {
method: 'DELETE',
}).then(async (response) => {
if (response.status === 200) {
tr.parentNode.removeChild(tr);
} else {
const error = livre["error"];
alert(error);
}
});
}
}
// modifie un livre basé sur son ID
function editLivre(id) {
const tr = document.querySelector('tr[data-id="' + id + '"]');
const attributes = {};
tr.querySelectorAll("td[data-name]").forEach((td) => {
const name = td.dataset["name"];
const value = td.textContent;
attributes[name] = value;
td.removeAttribute("data-name");
td.textContent = '';
let input = document.createElement("input");
input.setAttribute("data-name", name);
input.setAttribute("value", value);
td.append(input);
});
const btns = tr.querySelector('div.btn-group');
btns.querySelectorAll('a').forEach((a) => {
console.info(a);
if (a.dataset["action"] == "edit") {
a.setAttribute("data-action", "save");
a.removeAttribute("onclick");
a.setAttribute("onclick", "saveLivre("+id+")");
a.textContent = "Sauver";
}
if (a.dataset["action"] == "destroy") {
a.setAttribute("data-action", "cancel");
a.removeAttribute("onclick");
a.setAttribute("onclick", "cancel("+id+")");
a.textContent = "Annuler";
}
});
console.info(tr);
/*fetch ('/livres/' + id, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
confirm: {
class: 'btn btn-danger btn-sm',
html: '<span class="fa fa-check"></span> Confirm'
body: JSON.stringify(attributes)
})*/
/*.then(async (response) => {
if (response.status === 200) {
} else {
const error = livre["error"];
alert(error);
}
},
hideIdentifier: true,
columns: {
identifier: [0, 'id'],
editable: [[1, 'titre'], [2, 'auteur'], [3, 'synopsis'], [4, 'style'], [5, 'isbn']]
},
onSuccess: function(data, textStatus, jqXHR) {
console.log('onSuccess(data, textStatus, jqXHR)');
console.log(data);
console.log(textStatus);
console.log(jqXHR);
},
onAjax: function(action, serialize) {
console.log('onAjax(action, serialize)');
console.log(action);
console.log(serialize);
}
})*/;
}
// annuler la modification d’un livre
function cancel(id) {
const tr = document.querySelector('tr[data-id="' + id + '"]');
const attributes = {};
tr.querySelectorAll("input[data-name]").forEach((input) => {
const name = input.dataset["name"];
const value = input.value;
const td = input.offsetParent;
td.setAttribute("data-name", name);
td.textContent = value;
});
const btns = tr.querySelector('div.btn-group');
btns.querySelectorAll('a').forEach((a) => {
if (a.dataset["action"] == "save") {
a.setAttribute("data-action", "edit");
a.removeAttribute("onclick");
a.setAttribute("onclick", "editLivre("+id+")");
a.textContent = "Modifier";
}
if (a.dataset["action"] == "cancel") {
a.setAttribute("data-action", "destroy");
a.removeAttribute("onclick");
a.setAttribute("onclick", "deleteLivre("+id+")");
a.textContent = "Supprimer";
}
});
}
// ajoute un livre
document.addEventListener('DOMContentLoaded', async function () {
const table = document.querySelector("table");
const tbody = table.querySelector("tbody");
const trCreate = document.querySelector("tr.new");
const url = table.dataset["url"];
// ajout d’un livre
const createLink = document.querySelector('a[data-action="create"]');
createLink.addEventListener("click", async function () {
let attributes = {};
trCreate.querySelectorAll("td input").forEach((input) => {
const name = input.dataset["name"];
const value = input.value;
attributes[name] = value;
});
fetch (url, {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(attributes)
}).then(async (response) => {
response.json().then((livre) => {
if (response.status === 200) {
const tr = document.createElement("tr");
tr.dataset["id"] = livre["id"];
tbody.insertBefore(tr, tbody.firstChild);
for (const name in attributes) {
const td = document.createElement("td");
td.setAttribute("data-name", name);
td.textContent = livre[name];
tr.appendChild(td);
}
let td = document.createElement("td");
let a = document.createElement("a", );
a.setAttribute("data-action", "destroy");
a.setAttribute("href", "#");
a.setAttribute("onclick", "deleteLivre(" + livre["id"] + ")");
a.textContent = 'Supprimer';
td.appendChild(a);
tr.appendChild(td);
} else {
const error = livre["error"];
alert(error);
}
})
})
})
});

@ -1,2 +1,3 @@
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
end

@ -1,6 +1,10 @@
class LivresController < ApplicationController
before_action :set_livre, only: [:show, :edit, :update, :destroy]
rescue_from StandardError do |exception|
render json: { error: exception.message }, status: :bad_request
end
def index
@livres = Livre.order(id: :desc)
end
@ -13,20 +17,13 @@ class LivresController < ApplicationController
end
def create
@livre = Livre.new(livre_params)
respond_to do |format|
if @livre.save(livre_params)
format.js
format.html { redirect_to livres_path, notice: 'livre was successfully added.' }
format.json { render :index, status: :ok, location: livres_path }
else
format.html { render :new }
format.json { render json: @livre.errors, status: :unprocessable_entity }
end
end
@livre = Livre.create! params.require(:livre).permit :titre, :auteur, :synopsis, :style, :isbn
render json: @livre
end
def update
render json: @livre
=begin
respond_to do |format|
if @livre.update(livre_params)
format.js
@ -37,25 +34,8 @@ class LivresController < ApplicationController
format.json { render json: @livre.errors, status: :unprocessable_entity }
end
end
end
# interaction avec table-edit
=begin
def tabledit
if params[:edit]
@livre = Livre.find(params[:id])
respond_to do |format|
if @livre.update(livre_params)
format.html { redirect_to @livre, notice: 'livre was successfully updated.' }
format.json { render :index, status: :ok, location: livres_path }
else
format.html { render :edit }
format.json { render json: @livre.errors, status: :unprocessable_entity }
end
end
end
end
=end
end
def destroy
@livre.destroy
@ -64,7 +44,7 @@ class LivresController < ApplicationController
format.html { redirect_to livres_url, success: 'livre supprimé' }
format.json { head :no_content }
end
#redirect_to livres_url, success: 'livre supprimé'
#redirect_to livres_url, success: 'livre supprimé'
end
private

@ -1,2 +1,3 @@
class Livre < ApplicationRecord
validates_uniqueness_of :isbn
end

@ -12,7 +12,7 @@
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" id="mainmenu" role="navigation">
<a class="navbar-brand" href="/livres/index">Essai de tableau éditable en Rails</a>
<a class="navbar-brand" href="<%= livres_path %>">Essai de tableau éditable en Rails</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>

@ -2,7 +2,7 @@
<% if @livres.size > 0 %>
<div class="table-responsive">
<table id="livres" class="table table-bordered">
<table id="livres" class="table table-bordered" data-url="<%= livres_path %>">
<thead class="thead-dark">
<tr>
<th>Titre</th>
@ -12,15 +12,39 @@
<th>ISBN</th>
<th>Action</th>
</tr>
<tr class="new">
<% %i[titre auteur synopsis style isbn].each do |attr| %>
<td>
<input data-name="<%= attr %>"/>
</td>
<% end %>
<td>
<a class="btn btn-sm btn-dark" data-action="create" href="#">Ajouter</a>
</td>
</tr>
</thead>
<tbody>
<%= render @livres %>
<% @livres.each do |livre| %>
<tr data-id="<%= livre.id %>">
<td data-name="titre"><%= livre.titre %></td>
<td data-name="auteur"><%= livre.auteur %></td>
<td data-name="synopsis"><%= livre.synopsis[0..120] %></td>
<td data-name="style"><%= livre.style %></td>
<td data-name="isbn"><%= livre.isbn %></td>
<td>
<div class="btn-group" role="group">
<a class="btn btn-sm btn-dark" data-action="edit" href="#" onclick="editLivre(<%= livre.id %>)">Modifier</a>
<a class="btn btn-sm btn-dark" data-action="destroy" href="#" onclick="deleteLivre(<%= livre.id %>)">Supprimer</a>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
<div id="new-book">
<%= link_to 'Nouveau livre', new_livre_path, remote: true %>
</div>

@ -1,3 +1,114 @@
#!/usr/bin/env ruby
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
load Gem.bin_path('bundler', 'bundle')
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "rubygems"
m = Module.new do
module_function
def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__)
end
def env_var_version
ENV["BUNDLER_VERSION"]
end
def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = $1
update_index = i
end
bundler_version
end
def gemfile
gemfile = ENV["BUNDLE_GEMFILE"]
return gemfile if gemfile && !gemfile.empty?
File.expand_path("../../Gemfile", __FILE__)
end
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
end
def lockfile_version
return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1)
end
def bundler_version
@bundler_version ||=
env_var_version || cli_arg_version ||
lockfile_version
end
def bundler_requirement
return "#{Gem::Requirement.default}.a" unless bundler_version
bundler_gem_version = Gem::Version.new(bundler_version)
requirement = bundler_gem_version.approximate_recommendation
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
requirement += ".a" if bundler_gem_version.prerelease?
requirement
end
def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile
activate_bundler
end
def activate_bundler
gem_error = activation_error_handling do
gem "bundler", bundler_requirement
end
return if gem_error.nil?
require_error = activation_error_handling do
require "bundler/version"
end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42
end
def activation_error_handling
yield
nil
rescue StandardError, LoadError => e
e
end
end
m.load_bundler!
if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end

@ -0,0 +1,29 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'guard' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
bundle_binstub = File.expand_path("../bundle", __FILE__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("guard", "guard")

@ -58,4 +58,6 @@ Rails.application.configure do
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
config.middleware.insert_after ActionDispatch::Static, Rack::LiveReload
# config.middleware.insert_before Rack::Lock, Rack::LiveReload
end

Loading…
Cancel
Save