active-list 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,161 @@
1
+ // Default style for ActiveList
2
+ @import "compass/css3";
3
+ @import "compass/typography/text/replacement";
4
+
5
+ // Because Compass sprites do not seems to work well in gems
6
+ @mixin active-list-sprite($name, $size: 16px) {
7
+ background-image: image-url("active-list.png");
8
+ background-repeat: no-repeat;
9
+ height: $size;
10
+ width: $size;
11
+ $i: 0;
12
+ @each $icon in menu check columns export pages first-page previous-page previous-line next-line next-page last-page sort sort-down sort-up true false {
13
+ @if $icon == $name {
14
+ background-position: (-$i * $size) 0;
15
+ }
16
+ $i: $i + 1;
17
+ }
18
+ }
19
+
20
+
21
+ @mixin active-list-theme($theme-color: #777777) {
22
+ $default-border-color: mix($theme-color, #FFF, 30%);
23
+ table.list {
24
+ border-collapse: collapse;
25
+ width: 100%;
26
+ border: 1px solid $default-border-color;
27
+ z-index: 2;
28
+ @include box-shadow(0 0 7px rgba(0, 0, 0, 0.15));
29
+ thead {
30
+ th {
31
+ border: 1px solid $default-border-color;
32
+ padding: 2px 3px;
33
+ @include background($theme-color linear-gradient(top, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.8)));
34
+ &[data-list-column-sort] {
35
+ .icon {
36
+ @include active-list-sprite(sort);
37
+ @include inline-block;
38
+ margin: 0 2px;
39
+ }
40
+ &.sor[data-list-column-sort="desc"] .icon {
41
+ @include active-list-sprite(sort-down); }
42
+ &.sor[data-list-column-sort="asc"] .icon {
43
+ @include active-list-sprite(sort-up); }
44
+ }
45
+ html[dir="ltr"] & { text-align: left; }
46
+ html[dir="rtl"] & { text-align: right; }
47
+ &.spe {
48
+ width: 16px;
49
+ height: 16px;
50
+ .list-menu {
51
+ position: relative;
52
+ background: none;
53
+ .list-menu-start {
54
+ padding: 2px;
55
+ width: 16px;
56
+ height: 16px;
57
+ .icon { @include active-list-sprite(menu); }
58
+ .text { display: none; }
59
+ }
60
+ &:hover {
61
+ .list-menu-start {
62
+ background-color: white;
63
+ z-index:5000;
64
+ }
65
+ }
66
+ ul {
67
+ border: 1px solid $default-border-color;
68
+ background: #FFF;
69
+ @include box-shadow(0 0 5px rgba(0, 0, 0, 0.3));
70
+ li {
71
+ $menu-width: 290px;
72
+ width: $menu-width;
73
+ ul {
74
+ html[dir="ltr"] & { right: $menu-width; }
75
+ html[dir="rtl"] & { left: $menu-width; }
76
+ }
77
+ }
78
+ }
79
+ a, li {
80
+ display: block;
81
+ padding: 2px;
82
+ .icon {
83
+ @include inline-block;
84
+ height:16px;
85
+ width: 16px;
86
+ html[dir="ltr"] & { margin-right: 4px; }
87
+ html[dir="rtl"] & { margin-left: 4px; }
88
+ }
89
+ &.pages .icon { @include active-list-sprite(pages); }
90
+ &.export .icon { @include active-list-sprite(export); }
91
+ &.check .icon { @include active-list-sprite(check); }
92
+ &.columns .icon { @include active-list-sprite(columns); }
93
+ &.checked .icon { @include active-list-sprite(true); }
94
+ &.unchecked .icon { @include active-list-sprite(false); }
95
+ }
96
+ a:hover, li:hover {
97
+ text-decoration: none;
98
+ background: mix($theme-color, #FFF, 10%);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ tbody {
105
+ tr {
106
+ td {
107
+ padding: 2px 3px;
108
+ border-top: 1px solid mix($theme-color, #FFF, 10%);
109
+ border-bottom: none;
110
+ border-left: none;
111
+ border-right: none;
112
+ &.chk,&.act, &.dld, &.bln, &.dat, &.web { text-align:center }
113
+ &.dec, &.flt, &.int {
114
+ html[dir="ltr"] & { text-align: right; }
115
+ html[dir="rtl"] & { text-align: left; }
116
+ }
117
+ &.country { white-space: nowrap; }
118
+ &.color { color: white; text-shadow: 0 0 2px #000; width: 6ex; text-align: center; }
119
+ }
120
+ &:first-child td {
121
+ border-top: none;
122
+ }
123
+ }
124
+ }
125
+
126
+ @include list-colors(#FFFFFF);
127
+ }
128
+ .extras {
129
+ z-index: 0;
130
+ @include background(transparentize(mix($theme-color, #FFF, 40%), 0.7));
131
+ .pagination {
132
+ html[dir="ltr"] & { text-align: left; }
133
+ html[dir="rtl"] & { text-align: right; }
134
+ & > * {
135
+ margin: 0 2px;
136
+ }
137
+ .first-page, .previous-page, .next-page, .last-page {
138
+ @include inline-block;
139
+ @include squish-text;
140
+ }
141
+ html[dir="rtl"] & .last-page, html[dir="ltr"] & .first-page {
142
+ @include active-list-sprite(first-page);
143
+ }
144
+ html[dir="rtl"] & .first-page, html[dir="ltr"] & .last-page {
145
+ @include active-list-sprite(last-page);
146
+ }
147
+ html[dir="rtl"] & .next-page, html[dir="ltr"] & .previous-page {
148
+ @include active-list-sprite(previous-page);
149
+ }
150
+ html[dir="rtl"] & .previous-page, html[dir="ltr"] & .next-page {
151
+ @include active-list-sprite(next-page);
152
+ }
153
+ .separator {
154
+ @include inline-block;
155
+ width: 2px;
156
+ height: 1.2em;
157
+ background: transparentize($theme-color, 0.7);
158
+ }
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,103 @@
1
+ module ActiveList
2
+
3
+ class Table
4
+ attr_reader :name, :model, :options, :id, :columns, :parameters
5
+
6
+ @@current_id = 0
7
+
8
+ def initialize(name, model = nil, options = {})
9
+ @name = name
10
+ @model = model || name.to_s.classify.constantize
11
+ @options = options
12
+ @paginate = !(@options[:pagination]==:none || @options[:paginate].is_a?(FalseClass))
13
+ @options[:renderer] ||= :simple_renderer
14
+ @options[:per_page] = 20 if @options[:per_page].to_i <= 0
15
+ @options[:page] = 1 if @options[:page].to_i <= 0
16
+ @columns = []
17
+ @current_id = 0
18
+ @id = @@current_id.to_s(36).to_sym
19
+ @@current_id += 1
20
+ @parameters = {:sort=>:to_s, :dir=>:to_s}
21
+ @parameters.merge!(:page=>:to_i, :per_page=>:to_i) if self.paginate?
22
+ end
23
+
24
+ def new_id
25
+ id = @current_id.to_s(36).to_sym
26
+ @current_id += 1
27
+ return id
28
+ end
29
+
30
+ def model_columns
31
+ @model.columns
32
+ end
33
+
34
+ def sortable_columns
35
+ @columns.select{|c| c.sortable?}
36
+ end
37
+
38
+ def exportable_columns
39
+ @columns.select{|c| c.exportable?}
40
+ end
41
+
42
+ def paginate?
43
+ @paginate
44
+ end
45
+
46
+ def load_default_columns
47
+ for column in self.model_columns
48
+ reflections = @model.reflections.values.select{|r| r.macro == :belongs_to and r.foreign_key.to_s == column.name.to_s}
49
+ if reflections.size == 1
50
+ reflection = reflections.first
51
+ columns = reflection.class_name.constantize.columns.collect{ |c| c.name.to_s }
52
+ self.column([:label, :name, :code, :number].detect{ |l| columns.include?(l.to_s) }, :through => reflection.name, :url => true)
53
+ else
54
+ self.column(column.name)
55
+ end
56
+ end
57
+ return true
58
+ end
59
+
60
+ end
61
+
62
+
63
+ class Column
64
+ attr_accessor :name, :options, :table
65
+ attr_reader :id
66
+
67
+ def initialize(table, name, options={})
68
+ @table = table
69
+ @name = name
70
+ @options = options
71
+ @column = @table.model.columns.detect{|c| c.name.to_s == @name.to_s }
72
+ @id = @table.new_id
73
+ end
74
+
75
+ def header_code
76
+ raise NotImplementedError.new("#{self.class.name}#header_code is not implemented.")
77
+ end
78
+
79
+ def sortable?
80
+ false
81
+ end
82
+
83
+ def exportable?
84
+ false
85
+ end
86
+
87
+ # Unique identifier of the column in the application
88
+ def unique_id
89
+ "#{@table.name}-#{@id}"
90
+ end
91
+
92
+ # Uncommon but simple identifier for CSS class uses
93
+ def simple_id
94
+ "_#{@table.id}_#{@id}"
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+ require "active-list/columns/data_column"
102
+ require "active-list/columns/action_column"
103
+ require "active-list/columns/field_column"
@@ -0,0 +1,71 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ module ActiveList
4
+
5
+ mattr_reader :exporters
6
+ @@exporters = {}
7
+
8
+ def self.register_exporter(name, exporter)
9
+ raise ArgumentError.new("ActiveList::Exporter expected (got #{exporter.name}/#{exporter.ancestors.inspect})") unless exporter.ancestors.include? ActiveList::Exporter
10
+ @@exporters[name] = exporter.new(name)
11
+ end
12
+
13
+ class Exporter
14
+ attr_reader :name
15
+
16
+ def initialize(name)
17
+ @name = name
18
+ end
19
+
20
+ def file_extension
21
+ "txt"
22
+ end
23
+
24
+ def mime_type
25
+ Mime::TEXT
26
+ end
27
+
28
+ # Not used
29
+ # def condition
30
+ # "not request.xhr? and params[:format] == '#{name}'"
31
+ # end
32
+
33
+ def send_data_code(table)
34
+ raise NotImplementedError.new("#{self.class.name}#format_data_code is not implemented.")
35
+ end
36
+
37
+ def columns_headers(table, options={})
38
+ headers, columns = [], table.exportable_columns
39
+ for column in columns
40
+ datum = column.header_code
41
+ headers << (options[:iconv] ? "#{options[:iconv]}.iconv("+datum+".to_s)" : datum)
42
+ end
43
+ return headers
44
+ end
45
+
46
+ def columns_to_array(table, nature, options={})
47
+ columns = table.exportable_columns
48
+
49
+ array = []
50
+ record = options[:record]||'rekord'
51
+ for column in columns
52
+ if column.is_a? ActiveList::Column
53
+ if nature==:header
54
+ datum = column.header_code
55
+ else
56
+ datum = column.exporting_datum_code(record)
57
+ end
58
+ array << (options[:iconv] ? "#{options[:iconv]}.iconv("+datum+".to_s)" : datum)
59
+ end
60
+ end
61
+ return array
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
68
+
69
+ require "active-list/exporters/open_document_spreadsheet_exporter"
70
+ require "active-list/exporters/csv_exporter"
71
+ require "active-list/exporters/excel_csv_exporter"
@@ -0,0 +1,30 @@
1
+ module ActiveList
2
+
3
+ class CsvExporter < ActiveList::Exporter
4
+
5
+ def file_extension
6
+ "csv"
7
+ end
8
+
9
+ def mime_type
10
+ Mime::CSV
11
+ end
12
+
13
+ def send_data_code(table)
14
+ record = "r"
15
+ code = table.select_data_code(:paginate => false)
16
+ code += "data = ActiveList::CSV.generate do |csv|\n"
17
+ code += " csv << [#{columns_to_array(table, :header).join(', ')}]\n"
18
+ code += " for #{record} in #{table.records_variable_name}\n"
19
+ code += " csv << [#{columns_to_array(table, :body, :record=>record).join(', ')}]\n"
20
+ code += " end\n"
21
+ code += "end\n"
22
+ code += "send_data(data, :type=>#{self.mime_type.to_s.inspect}, :disposition=>'inline', :filename=>#{table.model.name}.model_name.human.gsub(/[^a-z0-9]/i,'_')+'.#{self.file_extension}')\n"
23
+ return code
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ ActiveList.register_exporter(:csv, ActiveList::CsvExporter)
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+
3
+ # Register XCSV format unless is already set
4
+ Mime::Type.register("text/csv", :xcsv) unless defined? Mime::XCSV
5
+
6
+ module ActiveList
7
+
8
+ class ExcelCsvExporter < ActiveList::CsvExporter
9
+
10
+ def file_extension
11
+ "csv"
12
+ end
13
+
14
+ def mime_type
15
+ Mime::XCSV
16
+ end
17
+
18
+ def send_data_code(table)
19
+ record = "r"
20
+ code = table.select_data_code(:paginate => false)
21
+ code += "ic = Iconv.new('cp1252', 'utf-8')\n"
22
+ code += "data = ActiveList::CSV.generate(:col_sep=>';') do |csv|\n"
23
+ code += " csv << [#{columns_to_array(table, :header, :iconv=>'ic').join(', ')}]\n"
24
+ code += " for #{record} in #{table.records_variable_name}\n"
25
+ code += " csv << [#{columns_to_array(table, :body, :record=>record, :iconv=>'ic').join(', ')}]\n"
26
+ code += " end\n"
27
+ code += "end\n"
28
+ code += "send_data(data, :type=>#{self.mime_type.to_s.inspect}, :disposition=>'inline', :filename=>#{table.model.name}.model_name.human.gsub(/[^a-z0-9]/i,'_')+'.#{self.file_extension}')\n"
29
+ return code
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ ActiveList.register_exporter(:xcsv, ActiveList::ExcelCsvExporter)
@@ -0,0 +1,81 @@
1
+ # encoding: UTF-8
2
+
3
+ # Register ODS format unless is already set
4
+ Mime::Type.register("application/vnd.oasis.opendocument.spreadsheet", :ods) unless defined? Mime::ODS
5
+
6
+ module ActiveList
7
+
8
+ class OpenDocumentSpreadsheetExporter < ActiveList::Exporter
9
+
10
+ DATE_ELEMENTS = {
11
+ "m" => "<number:month number:style=\"long\"/>",
12
+ "d" => "<number:day number:style=\"long\"/>",
13
+ "Y" => "<number:year/>"
14
+ }
15
+
16
+
17
+ def file_extension
18
+ "ods"
19
+ end
20
+
21
+ def mime_type
22
+ Mime::ODS
23
+ end
24
+
25
+ def send_data_code(table)
26
+ xml_escape = "to_s.gsub('&', '&amp;').gsub('\\'', '&apos;').gsub('<', '&lt;').gsub('>', '&gt;')"
27
+ xml_escape << ".force_encoding('US-ASCII')" if xml_escape.respond_to?(:force_encoding)
28
+ record = "r"
29
+ code = table.select_data_code(:paginate => false)
30
+ code << "name = #{table.model.name}.model_name.human.gsub(/[^a-z0-9]/i,'_')\n"
31
+ code << "temporary_dir = ::Rails.root.join('tmp', 'active-list')\n"
32
+ code << "FileUtils.mkdir_p(temporary_dir)\n"
33
+ code << "file = temporary_dir.join(name+rand.to_s+'.#{self.file_extension}')\n"
34
+ code << "Zip::ZipOutputStream.open(file) do |zile|\n"
35
+ # MimeType in first place
36
+ code << " zile.put_next_entry('mimetype', nil, nil, Zip::ZipEntry::STORED)\n"
37
+ code << " zile << '#{self.mime_type}'\n"
38
+
39
+ # Manifest
40
+ code << " zile.put_next_entry('META-INF/manifest.xml')\n"
41
+ code << " zile << ('<?xml version=\"1.0\" encoding=\"UTF-8\"?><manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\"><manifest:file-entry manifest:media-type=\"#{self.mime_type}\" manifest:full-path=\"/\"/><manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/></manifest:manifest>')\n"
42
+ code << " zile.put_next_entry('content.xml')\n"
43
+
44
+ code << " zile << ('<?xml version=\"1.0\" encoding=\"UTF-8\"?><office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:ooo=\"http://openoffice.org/2004/office\" xmlns:ooow=\"http://openoffice.org/2004/writer\" xmlns:oooc=\"http://openoffice.org/2004/calc\" xmlns:dom=\"http://www.w3.org/2001/xml-events\" xmlns:xforms=\"http://www.w3.org/2002/xforms\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:field=\"urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:field:1.0\" office:version=\"1.1\"><office:scripts/>')\n"
45
+ # Styles
46
+ code << " zile << ('<office:automatic-styles>"+
47
+ "<style:style style:name=\"co1\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:use-optimal-column-width=\"true\"/></style:style>"+
48
+ "<style:style style:name=\"header\" style:family=\"table-cell\"><style:text-properties fo:font-weight=\"bold\" style:font-weight-asian=\"bold\" style:font-weight-complex=\"bold\"/></style:style>"+
49
+ "<number:date-style style:name=\"K4D\" number:automatic-order=\"true\"><number:text>"+::I18n.translate("date.formats.default").gsub(/\%./){|x| "</number:text>"+DATE_ELEMENTS[x[1..1]]+"<number:text>"} +"</number:text></number:date-style><style:style style:name=\"ce1\" style:family=\"table-cell\" style:data-style-name=\"K4D\"/>"+
50
+ "</office:automatic-styles>')\n"
51
+
52
+ # Tables
53
+ code << " zile << ('<office:body><office:spreadsheet><table:table table:name=\"'+#{table.model.name}.model_name.human.#{xml_escape}+'\">')\n"
54
+ code << " zile << ('<table:table-column table:number-columns-repeated=\"#{table.exportable_columns.size}\"/>')\n"
55
+ code << " zile << ('<table:table-header-rows><table:table-row>"+columns_headers(table).collect{|h| "<table:table-cell table:style-name=\"header\" office:value-type=\"string\"><text:p>'+(#{h}).#{xml_escape}+'</text:p></table:table-cell>"}.join+"</table:table-row></table:table-header-rows>')\n"
56
+ code << " for #{record} in #{table.records_variable_name}\n"
57
+ code << " zile << ('<table:table-row>"+table.exportable_columns.collect do |column|
58
+ "<table:table-cell"+(if column.numeric? or column.datatype==:decimal
59
+ " office:value-type=\"float\" office:value=\"'+(#{column.datum_code(record)}).#{xml_escape}+'\""
60
+ elsif column.datatype==:boolean
61
+ " office:value-type=\"boolean\" office:boolean-value=\"'+(#{column.datum_code(record)}).#{xml_escape}+'\""
62
+ elsif column.datatype==:date
63
+ " office:value-type=\"date\" table:style-name=\"ce1\" office:date-value=\"'+(#{column.datum_code(record)}).#{xml_escape}+'\""
64
+ else
65
+ " office:value-type=\"string\""
66
+ end)+"><text:p>'+("+column.exporting_datum_code(record, true)+").#{xml_escape}+'</text:p></table:table-cell>"
67
+ end.join+"</table:table-row>')\n"
68
+ code << " end\n"
69
+ code << " zile << ('</table:table></office:spreadsheet></office:body></office:document-content>')\n"
70
+ code << "end\n"
71
+ code << "send_file(file, :stream=>false, :type=>#{self.mime_type.to_s.inspect}, :disposition=>'inline', :filename=>name+'.#{self.file_extension}')\n"
72
+ code << "File.delete(file)\n" # Removes tmp files before they explode the disk
73
+ # raise code
74
+ return code
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ ActiveList.register_exporter(:ods, ActiveList::OpenDocumentSpreadsheetExporter)