From b753eb173aba7fdd1fffae461f81f32dc9879e8b Mon Sep 17 00:00:00 2001
From: =?utf8?q?Jordan=20Aug=C3=A9?= <jordan.auge@lip6.fr>
Date: Thu, 26 Sep 2013 17:29:10 +0200
Subject: [PATCH] added new files for demo

---
 demo_trash/tabs/__init__.py                   |  19 +
 demo_trash/tabs/static/css/tabs.css           |   5 +
 demo_trash/tabs/static/js/tabs.js             |  12 +
 demo_trash/tabs/templates/tabs.html           |  12 +
 plugins/hazelnut.demo/__init__.py             |  58 ++
 plugins/hazelnut.demo/demo_page.css1          |  93 +++
 plugins/hazelnut.demo/static/css/hazelnut.css |  64 ++
 plugins/hazelnut.demo/static/hazelnut.html    |  33 +
 plugins/hazelnut.demo/static/img/README       |   2 +
 .../static/img/tablesort-bullet1.png          | Bin 0 -> 192 bytes
 .../static/img/tablesort-bullet2.png          | Bin 0 -> 464 bytes
 .../static/img/tablesort-col-alt.png          | Bin 0 -> 512 bytes
 .../static/img/tablesort-gradient.png         | Bin 0 -> 265 bytes
 .../static/img/tablesort-header-down.png      | Bin 0 -> 3845 bytes
 .../static/img/tablesort-header-sortable.png  | Bin 0 -> 3758 bytes
 .../static/img/tablesort-header-up.png        | Bin 0 -> 3877 bytes
 .../static/img/tablesort-header.png           | Bin 0 -> 2887 bytes
 .../static/img/tablesort-td-alt.png           | Bin 0 -> 479 bytes
 .../static/img/toggle-hidden.png              | Bin 0 -> 4356 bytes
 .../static/img/toggle-visible.png             | Bin 0 -> 4328 bytes
 plugins/hazelnut.demo/static/js/hazelnut.js   | 591 ++++++++++++++++++
 plugins/tabs/static/tabs.html                 |  12 +
 plugins/tabs/tabs.py                          |  19 +
 portal/demo_sliceview.py                      | 289 +++++++++
 .../datatables-1.9.4/js/datatables-bs3.js     | 383 ++++++++++++
 25 files changed, 1592 insertions(+)
 create mode 100644 demo_trash/tabs/__init__.py
 create mode 100644 demo_trash/tabs/static/css/tabs.css
 create mode 100644 demo_trash/tabs/static/js/tabs.js
 create mode 100644 demo_trash/tabs/templates/tabs.html
 create mode 100644 plugins/hazelnut.demo/__init__.py
 create mode 100644 plugins/hazelnut.demo/demo_page.css1
 create mode 100644 plugins/hazelnut.demo/static/css/hazelnut.css
 create mode 100644 plugins/hazelnut.demo/static/hazelnut.html
 create mode 100644 plugins/hazelnut.demo/static/img/README
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-bullet1.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-bullet2.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-col-alt.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-gradient.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-header-down.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-header-sortable.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-header-up.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-header.png
 create mode 100644 plugins/hazelnut.demo/static/img/tablesort-td-alt.png
 create mode 100755 plugins/hazelnut.demo/static/img/toggle-hidden.png
 create mode 100755 plugins/hazelnut.demo/static/img/toggle-visible.png
 create mode 100644 plugins/hazelnut.demo/static/js/hazelnut.js
 create mode 100644 plugins/tabs/static/tabs.html
 create mode 100644 plugins/tabs/tabs.py
 create mode 100644 portal/demo_sliceview.py
 create mode 100644 third-party/datatables-1.9.4/js/datatables-bs3.js

diff --git a/demo_trash/tabs/__init__.py b/demo_trash/tabs/__init__.py
new file mode 100644
index 00000000..6da94da1
--- /dev/null
+++ b/demo_trash/tabs/__init__.py
@@ -0,0 +1,19 @@
+from unfold.composite import Composite
+
+class Tabs (Composite):
+    
+    def requirements (self):
+        return { 'js_files'     : ['js/tabs.js', 'js/bootstrap.js'],
+                 'css_files'    : ['css/bootstrap.css', 'css/tabs.css', ] 
+                 }
+
+    def template_file (self):
+        return "tabs.html"
+
+    # see Composite.py for the details of template_env, that exposes global
+    # 'sons' as a list of sons with each a set of a few attributes
+    def json_settings_list (self):
+        return []
+
+    def export_json_settings(self):
+        return True
diff --git a/demo_trash/tabs/static/css/tabs.css b/demo_trash/tabs/static/css/tabs.css
new file mode 100644
index 00000000..3dc765a9
--- /dev/null
+++ b/demo_trash/tabs/static/css/tabs.css
@@ -0,0 +1,5 @@
+div.Tabs {
+/*    border-style: solid; 
+   border-color: #aaa; */
+    padding: 10px;
+}
diff --git a/demo_trash/tabs/static/js/tabs.js b/demo_trash/tabs/static/js/tabs.js
new file mode 100644
index 00000000..b5b1cd0b
--- /dev/null
+++ b/demo_trash/tabs/static/js/tabs.js
@@ -0,0 +1,12 @@
+(function($){
+
+    $.fn.Tabs = function( method ) {
+
+        $('a[data-toggle="tab"]').on('shown', function (e) {
+          // find the plugin object inside the tab content referenced by the current tabs
+          $('.plugin', $($(e.target).attr('href'))).trigger('show');
+        });
+
+    };
+
+})( jQuery );
diff --git a/demo_trash/tabs/templates/tabs.html b/demo_trash/tabs/templates/tabs.html
new file mode 100644
index 00000000..298dec92
--- /dev/null
+++ b/demo_trash/tabs/templates/tabs.html
@@ -0,0 +1,12 @@
+<ul class="nav nav-tabs" id='tabs-{{ domid }}'>
+{% for son in sons %}
+<li{% if son.is_active %} class='active'{% endif %}> <a href="#tab-{{ son.domid }}" data-toggle="tab">{{ son.title }}</a> </li>
+{% endfor %}
+</ul><!--nav-tabs-->
+<div class="tab-content">
+{% for son in sons %}
+<div class="tab-pane fade in{% if son.is_active %} active{% endif %}" id="tab-{{ son.domid }}">
+{{ son.rendered }}
+</div><!--tab-pane-->
+{% endfor %}
+</div><!--tab-content-->
diff --git a/plugins/hazelnut.demo/__init__.py b/plugins/hazelnut.demo/__init__.py
new file mode 100644
index 00000000..2402588f
--- /dev/null
+++ b/plugins/hazelnut.demo/__init__.py
@@ -0,0 +1,58 @@
+from unfold.plugin import Plugin
+
+class Hazelnut (Plugin):
+
+    # set checkboxes if a final column with checkboxes is desired
+    # pass columns as the initial set of columns
+    #   if None then this is taken from the query's fields
+    def __init__ (self, query=None, query_all=None, checkboxes=False, columns=None, datatables_options={}, **settings):
+        Plugin.__init__ (self, **settings)
+        self.query          = query
+        # Until we have a proper way to access queries in Python
+        self.query_all      = query_all
+        self.query_all_uuid = query_all.query_uuid if query_all else None
+        self.checkboxes=checkboxes
+        # XXX We need to have some hidden columns until we properly handle dynamic queries
+        if columns is not None:
+            self.columns=columns
+            self.hidden_columns = []
+        elif self.query:
+            self.columns = self.query.fields
+            if query_all:
+                # We need a list because sets are not JSON-serilizable
+                self.hidden_columns = list(self.query_all.fields - self.query.fields)
+            else:
+                self.hidden_columns = []
+        else:
+            self.columns = []
+            self.hidden_columns = []
+        self.datatables_options=datatables_options
+
+    def template_file (self):
+        return "hazelnut.html"
+
+    def template_env (self, request):
+        env={}
+        env.update(self.__dict__)
+        env['columns']=self.columns
+        return env
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [ "js/hazelnut.js", 
+                           "js/manifold.js", "js/manifold-query.js", 
+                           #"js/dataTables.js", "js/with-datatables.js",
+                           "js/dataTables.js", "js/dataTables.bootstrap.js", "js/with-datatables.js",
+                           "js/datatables-bs3.js",
+                           "js/spin.presets.js", "js/spin.min.js", "js/jquery.spin.js", 
+                           "js/unfold-helper.js",
+                           ] ,
+            'css_files': [ "css/hazelnut.css" , 
+                           "css/dataTables.bootstrap.css",
+                           ],
+            }
+        return reqs
+
+    # the list of things passed to the js plugin
+    def json_settings_list (self):
+        return ['plugin_uuid', 'domid', 'query_uuid', 'query_all_uuid', 'checkboxes', 'datatables_options', 'hidden_columns']
diff --git a/plugins/hazelnut.demo/demo_page.css1 b/plugins/hazelnut.demo/demo_page.css1
new file mode 100644
index 00000000..bee7b0d9
--- /dev/null
+++ b/plugins/hazelnut.demo/demo_page.css1
@@ -0,0 +1,93 @@
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * General page setup
+ */
+#dt_example {
+	font: 80%/1.45em "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+	margin: 0;
+	padding: 0;
+	color: #333;
+	background-color: #fff;
+}
+
+
+#dt_example #container {
+	width: 800px;
+	margin: 30px auto;
+	padding: 0;
+}
+
+
+#dt_example #footer {
+	margin: 50px auto 0 auto;
+	padding: 0;
+}
+
+#dt_example #demo {
+	margin: 30px auto 0 auto;
+}
+
+#dt_example .demo_jui {
+	margin: 30px auto 0 auto;
+}
+
+#dt_example .big {
+	font-size: 1.3em;
+	font-weight: bold;
+	line-height: 1.6em;
+	color: #4E6CA3;
+}
+
+#dt_example .spacer {
+	height: 20px;
+	clear: both;
+}
+
+#dt_example .clear {
+	clear: both;
+}
+
+#dt_example pre {
+	padding: 15px;
+	background-color: #F5F5F5;
+	border: 1px solid #CCCCCC;
+}
+
+#dt_example h1 {
+	margin-top: 2em;
+	font-size: 1.3em;
+	font-weight: normal;
+	line-height: 1.6em;
+	color: #4E6CA3;
+	border-bottom: 1px solid #B0BED9;
+	clear: both;
+}
+
+#dt_example h2 {
+	font-size: 1.2em;
+	font-weight: normal;
+	line-height: 1.6em;
+	color: #4E6CA3;
+	clear: both;
+}
+
+#dt_example a {
+	color: #0063DC;
+	text-decoration: none;
+}
+
+#dt_example a:hover {
+	text-decoration: underline;
+}
+
+#dt_example ul {
+	color: #4E6CA3;
+}
+
+.css_right {
+	float: right;
+}
+
+.css_left {
+	float: left;
+}
\ No newline at end of file
diff --git a/plugins/hazelnut.demo/static/css/hazelnut.css b/plugins/hazelnut.demo/static/css/hazelnut.css
new file mode 100644
index 00000000..e4264d89
--- /dev/null
+++ b/plugins/hazelnut.demo/static/css/hazelnut.css
@@ -0,0 +1,64 @@
+.table {
+	width: 100% !important;
+}
+
+div.hazelnut-spacer {
+    height:10px;
+    clear: both;
+}
+
+div.Hazelnut table.dataTable th {
+    font: bold 12px/22px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
+    color: #4f6b72;
+    border-right: 1px solid #C1DAD7;
+    border-bottom: 1px solid #C1DAD7;
+    border-top: 1px solid #C1DAD7;
+    letter-spacing: 1px;
+    text-transform: uppercase;
+    text-align: left;
+    padding: 8px 12px 4px 20px;
+    vertical-align:middle;
+/*    background: #CAE8EA url(../img/tablesort-header.jpg) no-repeat; */
+}
+
+div.Hazelnut table.dataTable td, div.Hazelnut table.dataTable textarea, div.Hazelnut table.dataTable input [type="text"] {
+    font: normal 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
+    border-right: 1px solid #C1DAD7;
+    border-bottom: 1px solid #C1DAD7;
+}
+div.Hazelnut table.dataTable td {
+    padding: 4px 8px 4px 8px;
+    /* this applies on even rows only, odd ones have a setting in bootstrap of rbg 249.249.249 */
+    background-color: #f4f4f4;
+}
+div.Hazelnut table.dataTable td a {
+    font-weight:normal;
+}
+/* these come from bootstrap */
+div.Hazelnut div.dataTables_info {
+    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+/* one could think or using repeat-x here but that's not working because of the arrows
+ * we might need to make these wider some day 
+ * and/or to add background-color: #caebea
+ * which would look less conspicuous in case of overflow
+*/
+
+div.Hazelnut table.dataTable thead .sorting { background: url('../img/tablesort-header-sortable.png') no-repeat; }
+div.Hazelnut table.dataTable thead .sorting_asc { background: url('../img/tablesort-header-up.png') no-repeat; }
+div.Hazelnut table.dataTable thead .sorting_desc { background: url('../img/tablesort-header-down.png') no-repeat; }
+/* this icons set does not have that exact equivalent - using an approximation for now */
+div.Hazelnut table.dataTable thead .sorting_asc_disabled { background: url('../img/tablesort-header.png') no-repeat; }
+div.Hazelnut table.dataTable thead .sorting_desc_disabled { background: url('../img/tablesort-header.png') no-repeat; }
+
+/* the footers are not active */
+div.Hazelnut table.dataTable tfoot { 
+    background: url('../img/tablesort-header.png') no-repeat;
+    background-color: #caebea;
+}
+/* and when sorting is turned off it's useful to set this on header too */
+div.Hazelnut table.dataTable thead { 
+    background: url('../img/tablesort-header.png') no-repeat;
+    background-color: #caebea;
+}
diff --git a/plugins/hazelnut.demo/static/hazelnut.html b/plugins/hazelnut.demo/static/hazelnut.html
new file mode 100644
index 00000000..b49161b6
--- /dev/null
+++ b/plugins/hazelnut.demo/static/hazelnut.html
@@ -0,0 +1,33 @@
+<div id='main-{{ domid }}'>
+  <table class='table table-striped table-bordered dataTable' id='{{domid}}__table'>
+    <thead>
+      <tr>
+        {% for column in columns %}
+        <th>{{ column }}</th>
+        {% endfor %} 
+        {% for column in hidden_columns %}
+        <th>{{ column }}</th>
+        {% endfor %} 
+        {% if checkboxes %}
+		<th>+/-</th>
+		{% endif %}
+	  </tr>
+	</thead> 
+    <tbody>
+    </tbody>
+    <tfoot>
+      <tr>
+        {% for column in columns %}
+	    <th>{{ column }}</th>
+        {% endfor %} 
+        {% for column in hidden_columns %}
+	    <th>{{ column }}</th>
+        {% endfor %} 
+        {% if checkboxes %}
+		<th>+/-</th>
+		{% endif %}
+	  </tr>
+    </tfoot> 
+  </table>
+</div>
+<div class="hazelnut-spacer"></div>
diff --git a/plugins/hazelnut.demo/static/img/README b/plugins/hazelnut.demo/static/img/README
new file mode 100644
index 00000000..5df2d69b
--- /dev/null
+++ b/plugins/hazelnut.demo/static/img/README
@@ -0,0 +1,2 @@
+these styling elements come from plekit with a simple transition to png
+they're currently not all used in myslice
diff --git a/plugins/hazelnut.demo/static/img/tablesort-bullet1.png b/plugins/hazelnut.demo/static/img/tablesort-bullet1.png
new file mode 100644
index 0000000000000000000000000000000000000000..4304f360ff514f28b19261938ad13761c3c40c34
GIT binary patch
literal 192
zcmeAS@N?(olHy`uVBq!ia0y~yVBln6VBq3lV_;xNV=dVS5-9M9EM{Qf76xHPhFNnY
z7#J9eJY5_^BqWm)7KkOZNPc<n=iPrD9{YoL{`2_o{Fj&^S!cY-c!h+8!KMQujUpTV
z{^y#)qQ$`Vzgdgnr13$K#uxv4&hY$~j5wfnLb~E$ZsTm0O^@_XaH*BCrLZcTEpSyZ
sZ&>3H&Je`HUalwega7%39!7>Qp3<Kga#kH?U|?YIboFyt=akR{05*j_X#fBK

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-bullet2.png b/plugins/hazelnut.demo/static/img/tablesort-bullet2.png
new file mode 100644
index 0000000000000000000000000000000000000000..4f181e19fcee46275781f8619c3a0445b68e5d63
GIT binary patch
literal 464
zcmeAS@N?(olHy`uVBq!ia0y~yVAN({V9?-TVqjp<RQ3MJz`(#+;1OBOz`!jG!i)^F
z=14FwFgAI*IEGX(zP)Lf#gr)0deNtV@!g@bJMQGD?e<7bHuU4rXp?+;=<mi45iExN
z)&&m^AIhBfoL`@bB~E2S^t@`T`SD*$`c*%?W7^NEw|@Qm`TNbU?>~O{->-YpOe}1i
zHS6A;%Ab0)o#hpiHtVVN>vAR>n0DYl)6?y$Pm7t9S>AC?xzCX_n?;V}4%d|X%xeFY
z3_KE|>iEB8A5n<e%rcMb&OYY(^Q;sc7U(TFV71bbsj-2HS!X%RD<-ZrlciJyI5Z3n
f6nPDCN%5tx1-IPlvtndmU|{fc^>bP0l+XkK)_<gI

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-col-alt.png b/plugins/hazelnut.demo/static/img/tablesort-col-alt.png
new file mode 100644
index 0000000000000000000000000000000000000000..8179f8302f0aa6b7951d3e041eeaf0083505aa95
GIT binary patch
literal 512
zcmeAS@N?(olHy`uVBq!ia0y~yVAN({V9?-TVqjp<RQ3MJz`(#+;1OBOz`!jG!i)^F
z=14FwFmCd6aSW+oe0$TeugO7xHQ?yCf6Ggaxz4Z~73y91Ir}>Ingt6dP5+g7;CK4{
z`g`-A7vIU>{<rLO;c54-5HUuk$*jkIZ{8QT-q20z?eBjTHrGmh_sUjo;SiXiy`kr$
z$Deo4cSS9YnU<Ot9j@;1K*S=?ZvED|+8<B*%`n#dx9_88!vUSfiCa{QA~_S@*$eLx
zxyfA4I4AYpE|z^v$K__E@1NuGZBvkXo#wUL-~YT^8xF`cPBfo?{aSf$b7%aw11bfR
z4_H20=k;L!fsb4!+7nNIy!AIIUAJ66*-wg{$(Z$%*q0xV70)+##?Jd&^m*o8{g0=o
zS!`aPEv!($apT1q#>Qn0EM{N7swp(4H8=!2FwOo|B|O2w*kOUDLZff(UQUk%k_s0@
zCLCC{?;eZF1s;J6E{}xZ_<Saz3^ooER+SAa*PAnPnlQ7>ViLM><@)RfmRXEUzKonX
luU`8)Fr8(fiSdo`msf=NObLq&VPIfj@O1TaS?83{1OWZ+&};wz

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-gradient.png b/plugins/hazelnut.demo/static/img/tablesort-gradient.png
new file mode 100644
index 0000000000000000000000000000000000000000..26558a4948ca468b8e9bc4bb30b8f3315535faf4
GIT binary patch
literal 265
zcmeAS@N?(olHy`uVBq!ia0y~yU{GUVV2I{mV_;yAZ~VTIfq{Xuz$3Dlfq`2Xgc%uT
z&5>YWVA$>H;uw;_`u5sE-ev~@7RULn|03gL6ec>>Uw9Q#cRo4R_}H4g-~USg_{qQL
zrR~YYtur?!U5{E_H`D)9^3h{*%T8JOgvu!|JC(fZi_Gd!q18!p;zw3FoV_ivQ7hoH
zncEt!D|>oF4c{l@7QA7Yv??T3bVX(TqtG+!KS^xd7;1Iq7`hpPtC>vmkxdCZDyQtm
X^6dWgdutgO7#KWV{an^LB{Ts5b7gL4

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-header-down.png b/plugins/hazelnut.demo/static/img/tablesort-header-down.png
new file mode 100644
index 0000000000000000000000000000000000000000..c8ed65763971cc25327995cd01f8caa473cae7c3
GIT binary patch
literal 3845
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
Hu0mjfbe)_L

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-header-sortable.png b/plugins/hazelnut.demo/static/img/tablesort-header-sortable.png
new file mode 100644
index 0000000000000000000000000000000000000000..0c169040d877c305d38f32654bf7e9bf7129d98f
GIT binary patch
literal 3758
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
YKbWq-ms_8Wga7~l07*qoM6N<$f@fiU0RR91

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-header-up.png b/plugins/hazelnut.demo/static/img/tablesort-header-up.png
new file mode 100644
index 0000000000000000000000000000000000000000..d12fe2a5cbec852a02dc1d7aadd46c31c7022287
GIT binary patch
literal 3877
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

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-header.png b/plugins/hazelnut.demo/static/img/tablesort-header.png
new file mode 100644
index 0000000000000000000000000000000000000000..cff526f9c7beb9cb552a669b3c8171583a80c55c
GIT binary patch
literal 2887
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

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/tablesort-td-alt.png b/plugins/hazelnut.demo/static/img/tablesort-td-alt.png
new file mode 100644
index 0000000000000000000000000000000000000000..ef5ab358abd77cc238276d77592083cf7c0b5439
GIT binary patch
literal 479
zcmeAS@N?(olHy`uVBq!ia0y~yVAN({V9?-TVqjp<RQ3MJz`(#+;1OBOz`!jG!i)^F
z=14FwFi!MzaSW+oe0%fYx+Vh=mJ8Fq{NEq(;I7jd<27qHTOQHal`v_t^}?zDtj=G%
zRk-b5^nSlrudm$>l;>k&5##RJ^)K80)r;$a>R(T9%XOc9wnRW-LrB7vyX&L2zpvi9
zZqm8e(<0QRc3io8l=BW#vYuV_`d_v8n4eoJGO=9a?#a3pyZ!iXwsps+J1pqE_4caP
z7P0x?!#><&7wZXK@lxRfbK~2+?|(Z^<6vT$$Fx|^=Fb0vXZGH&{$Bp~(f$kS)84b}
z(=&MW&{@~wwW)c_|L*fq)3P31-x@pNKyZRxV}XFe1fdHDrtFDf3U**V%h<T2flcje
zs_+E|V+RGz1&yAyH(5+B@CbNtWh4Z}n=$g5FtbczlA3X4x~~KCS%!g9{)e%b!NKL;
TzK9+M1_lOCS3j3^P6<r_^9R8&

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/toggle-hidden.png b/plugins/hazelnut.demo/static/img/toggle-hidden.png
new file mode 100755
index 0000000000000000000000000000000000000000..023f22a8d8ffb61841939f0e5b60dc13a70ead9a
GIT binary patch
literal 4356
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

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/img/toggle-visible.png b/plugins/hazelnut.demo/static/img/toggle-visible.png
new file mode 100755
index 0000000000000000000000000000000000000000..baf6c286923f37e744bf4921a88976c612b4352e
GIT binary patch
literal 4328
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
Wx704#E!RQ-0000<MNUMnLSTZ^3nMcC

literal 0
HcmV?d00001

diff --git a/plugins/hazelnut.demo/static/js/hazelnut.js b/plugins/hazelnut.demo/static/js/hazelnut.js
new file mode 100644
index 00000000..748a0a5d
--- /dev/null
+++ b/plugins/hazelnut.demo/static/js/hazelnut.js
@@ -0,0 +1,591 @@
+/**
+ * Description: display a query result in a datatables-powered <table>
+ * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA
+ * License: GPLv3
+ */
+
+(function($){
+
+    // TEMP
+    var debug=false;
+    debug=true
+
+    var Hazelnut = Plugin.extend({
+
+        init: function(options, element) 
+        {
+            this._super(options, element);
+
+            /* Member variables */
+            // query status
+            this.received_all = false;
+            this.received_set = false;
+            this.in_set_buffer = Array();
+
+            /* XXX Events XXX */
+            // this.$element.on('show.Datatables', this.on_show);
+            this.elmt().on('show', this, this.on_show);
+            // Unbind all events using namespacing
+            // TODO in destructor
+            // $(window).unbind('Hazelnut');
+
+            var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
+            this.method = query.object;
+
+            var keys = manifold.metadata.get_key(this.method);
+            this.key = (keys && keys.length == 1) ? keys[0] : null;
+
+            /* Setup query and record handlers */
+            this.listen_query(options.query_uuid);
+            this.listen_query(options.query_all_uuid, 'all');
+
+            /* GUI setup and event binding */
+            this.initialize_table();
+        },
+
+        default_options: {
+            'checkboxes': false
+        },
+
+        /* PLUGIN EVENTS */
+
+        on_show: function(e)
+        {
+            var self = e.data;
+
+            self.table.fnAdjustColumnSizing()
+        
+            /* Refresh dataTabeles if click on the menu to display it : fix dataTables 1.9.x Bug */        
+            /* temp disabled... useful ? -- jordan
+            $(this).each(function(i,elt) {
+                if (jQuery(elt).hasClass('dataTables')) {
+                    var myDiv=jQuery('#hazelnut-' + this.id).parent();
+                    if(myDiv.height()==0) {
+                        var oTable=$('#hazelnut-' + this.id).dataTable();            
+                        oTable.fnDraw();
+                    }
+                }
+            });
+            */
+        }, // on_show
+
+        /* GUI EVENTS */
+
+        /* GUI MANIPULATION */
+
+        initialize_table: function() 
+        {
+            /* Transforms the table into DataTable, and keep a pointer to it */
+            var self = this;
+            actual_options = {
+                // Customize the position of Datatables elements (length,filter,button,...)
+                // we use a fluid row on top and another on the bottom, making sure we take 12 grid elt's each time
+                sDom: "<'row-fluid'<'span5'l><'span1'r><'span6'f>>t<'row-fluid'<'span5'i><'span7'p>>",
+                sPaginationType: 'bootstrap',
+                // Handle the null values & the error : Datatables warning Requested unknown parameter
+                // http://datatables.net/forums/discussion/5331/datatables-warning-...-requested-unknown-parameter/p2
+                aoColumnDefs: [{sDefaultContent: '',aTargets: [ '_all' ]}],
+                // WARNING: this one causes tables in a 'tabs' that are not exposed at the time this is run to show up empty
+                // sScrollX: '100%',       /* Horizontal scrolling */
+                bProcessing: true,      /* Loading */
+                fnDrawCallback: function() { self._hazelnut_draw_callback.call(self); }
+                // XXX use $.proxy here !
+            };
+            // the intention here is that options.datatables_options as coming from the python object take precedence
+            //  XXX DISABLED by jordan: was causing errors in datatables.js     $.extend(actual_options, options.datatables_options );
+            this.table = this.elmt('table').dataTable(actual_options);
+
+            /* Setup the SelectAll button in the dataTable header */
+            /* xxx not sure this is still working */
+            var oSelectAll = $('#datatableSelectAll-'+ this.options.plugin_uuid);
+            oSelectAll.html("<span class='ui-icon ui-icon-check' style='float:right;display:inline-block;'></span>Select All");
+            oSelectAll.button();
+            oSelectAll.css('font-size','11px');
+            oSelectAll.css('float','right');
+            oSelectAll.css('margin-right','15px');
+            oSelectAll.css('margin-bottom','5px');
+            oSelectAll.unbind('click');
+            oSelectAll.click(this._selectAll);
+
+            /* Add a filtering function to the current table 
+             * Note: we use closure to get access to the 'options'
+             */
+            $.fn.dataTableExt.afnFiltering.push(function( oSettings, aData, iDataIndex ) { 
+                /* No filtering if the table does not match */
+                if (oSettings.nTable.id != "hazelnut-" + self.options.plugin_uuid)
+                    return true;
+                return this._hazelnut_filter.call(self, oSettings, aData, iDataIndex);
+            });
+
+            /* Processing hidden_columns */
+            $.each(this.options.hidden_columns, function(i, field) {
+                self.hide_column(field);
+            });
+        }, // initialize_table
+
+        /**
+         * @brief Determine index of key in the table columns 
+         * @param key
+         * @param cols
+         */
+        getColIndex: function(key, cols) {
+            var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; });
+            return (tabIndex.length > 0) ? tabIndex[0] : -1;
+        }, // getColIndex
+
+ // UNUSED ? //         this.update_plugin = function(e, rows) {
+ // UNUSED ? //             // e.data is what we passed in second argument to subscribe
+ // UNUSED ? //             // so here it is the jquery object attached to the plugin <div>
+ // UNUSED ? //             var $plugindiv=e.data;
+ // UNUSED ? //             if (debug)
+ // UNUSED ? //                 messages.debug("entering hazelnut.update_plugin on id '" + $plugindiv.attr('id') + "'");
+ // UNUSED ? //             // clear the spinning wheel: look up an ancestor that has the need-spin class
+ // UNUSED ? //             // do this before we might return
+ // UNUSED ? //             $plugindiv.closest('.need-spin').spin(false);
+ // UNUSED ? // 
+ // UNUSED ? //             var options = this.options;
+ // UNUSED ? //             var hazelnut = this;
+ // UNUSED ? //     
+ // UNUSED ? //             /* if we get no result, or an error, try to make that clear, and exit */
+ // UNUSED ? //             if (rows.length==0) {
+ // UNUSED ? //                 if (debug) 
+ // UNUSED ? //                     messages.debug("Empty result on hazelnut " + this.options.domid);
+ // UNUSED ? //                 var placeholder=$(this.table).find("td.dataTables_empty");
+ // UNUSED ? //                 console.log("placeholder "+placeholder);
+ // UNUSED ? //                 if (placeholder.length==1) 
+ // UNUSED ? //                     placeholder.html(unfold.warning("Empty result"));
+ // UNUSED ? //                 else
+ // UNUSED ? //                     this.table.html(unfold.warning("Empty result"));
+ // UNUSED ? //                     return;
+ // UNUSED ? //             } else if (typeof(rows[0].error) != 'undefined') {
+ // UNUSED ? //                 // we now should have another means to report errors that this inline/embedded hack
+ // UNUSED ? //                 if (debug) 
+ // UNUSED ? //                     messages.error ("undefined result on " + this.options.domid + " - should not happen anymore");
+ // UNUSED ? //                 this.table.html(unfold.error(rows[0].error));
+ // UNUSED ? //                 return;
+ // UNUSED ? //             }
+ // UNUSED ? // 
+ // UNUSED ? //             /* 
+ // UNUSED ? //              * fill the dataTables object
+ // UNUSED ? //              * we cannot set html content directly here, need to use fnAddData
+ // UNUSED ? //              */
+ // UNUSED ? //             var lines = new Array();
+ // UNUSED ? //     
+ // UNUSED ? //             this.current_resources = Array();
+ // UNUSED ? //     
+ // UNUSED ? //             $.each(rows, function(index, row) {
+ // UNUSED ? //                 // this models a line in dataTables, each element in the line describes a cell
+ // UNUSED ? //                 line = new Array();
+ // UNUSED ? //      
+ // UNUSED ? //                 // go through table headers to get column names we want
+ // UNUSED ? //                 // in order (we have temporarily hack some adjustments in names)
+ // UNUSED ? //                 var cols = object.table.fnSettings().aoColumns;
+ // UNUSED ? //                 var colnames = cols.map(function(x) {return x.sTitle})
+ // UNUSED ? //                 var nb_col = cols.length;
+ // UNUSED ? //                 /* if we've requested checkboxes, then forget about the checkbox column for now */
+ // UNUSED ? //                 if (options.checkboxes) nb_col -= 1;
+ // UNUSED ? // 
+ // UNUSED ? //                 /* fill in stuff depending on the column name */
+ // UNUSED ? //                 for (var j = 0; j < nb_col; j++) {
+ // UNUSED ? //                     if (typeof colnames[j] == 'undefined') {
+ // UNUSED ? //                         line.push('...');
+ // UNUSED ? //                     } else if (colnames[j] == 'hostname') {
+ // UNUSED ? //                         if (row['type'] == 'resource,link')
+ // UNUSED ? //                             //TODO: we need to add source/destination for links
+ // UNUSED ? //                             line.push('');
+ // UNUSED ? //                         else
+ // UNUSED ? //                             line.push(row['hostname']);
+ // UNUSED ? //                     } else {
+ // UNUSED ? //                         if (row[colnames[j]])
+ // UNUSED ? //                             line.push(row[colnames[j]]);
+ // UNUSED ? //                         else
+ // UNUSED ? //                             line.push('');
+ // UNUSED ? //                     }
+ // UNUSED ? //                 }
+ // UNUSED ? //     
+ // UNUSED ? //                 /* catch up with the last column if checkboxes were requested */
+ // UNUSED ? //                 if (options.checkboxes) {
+ // UNUSED ? //                     var checked = '';
+ // UNUSED ? //                     // xxx problem is, we don't get this 'sliver' thing set apparently
+ // UNUSED ? //                     if (typeof(row['sliver']) != 'undefined') { /* It is equal to null when <sliver/> is present */
+ // UNUSED ? //                         checked = 'checked ';
+ // UNUSED ? //                         hazelnut.current_resources.push(row[ELEMENT_KEY]);
+ // UNUSED ? //                     }
+ // UNUSED ? //                     // Use a key instead of hostname (hard coded...)
+ // UNUSED ? //                     line.push(hazelnut.checkbox(options.plugin_uuid, row[ELEMENT_KEY], row['type'], checked, false));
+ // UNUSED ? //                 }
+ // UNUSED ? //     
+ // UNUSED ? //                 lines.push(line);
+ // UNUSED ? //     
+ // UNUSED ? //             });
+ // UNUSED ? //     
+ // UNUSED ? //             this.table.fnClearTable();
+ // UNUSED ? //             if (debug)
+ // UNUSED ? //                 messages.debug("hazelnut.update_plugin: total of " + lines.length + " rows");
+ // UNUSED ? //             this.table.fnAddData(lines);
+ // UNUSED ? //         
+ // UNUSED ? //         }, // update_plugin
+
+        checkbox: function (key, value)
+        {
+            var result="";
+            // Prefix id with plugin_uuid
+            result += "<input";
+            result += " class='hazelnut-checkbox'";
+            result += " id='" + this.id('checkbox', this.id_from_key(key, value)) + "'";
+            result += " name='" + key + "'";
+            result += " type='checkbox'";
+            result += " autocomplete='off'";
+            result += " value='" + value + "'";
+            result += "></input>";
+            return result;
+        }, // checkbox
+
+
+        new_record: function(record)
+        {
+            // this models a line in dataTables, each element in the line describes a cell
+            line = new Array();
+     
+            // go through table headers to get column names we want
+            // in order (we have temporarily hack some adjustments in names)
+            var cols = this.table.fnSettings().aoColumns;
+            var colnames = cols.map(function(x) {return x.sTitle})
+            var nb_col = cols.length;
+            /* if we've requested checkboxes, then forget about the checkbox column for now */
+            if (this.options.checkboxes) nb_col -= 1;
+
+            /* fill in stuff depending on the column name */
+            for (var j = 0; j < nb_col; j++) {
+                if (typeof colnames[j] == 'undefined') {
+                    line.push('...');
+                } else if (colnames[j] == 'hostname') {
+                    if (record['type'] == 'resource,link')
+                        //TODO: we need to add source/destination for links
+                        line.push('');
+                    else
+                        line.push(record['hostname']);
+                } else {
+                    if (record[colnames[j]])
+                        line.push(record[colnames[j]]);
+                    else
+                        line.push('');
+                }
+            }
+    
+            /* catch up with the last column if checkboxes were requested */
+            if (this.options.checkboxes)
+                // Use a key instead of hostname (hard coded...)
+                // XXX remove the empty checked attribute
+                line.push(this.checkbox(this.key, record[this.key]));
+    
+            // XXX Is adding an array of lines more efficient ?
+            this.table.fnAddData(line);
+
+        },
+
+        clear_table: function()
+        {
+            this.table.fnClearTable();
+        },
+
+        redraw_table: function()
+        {
+            this.table.fnDraw();
+        },
+
+        show_column: function(field)
+        {
+            var oSettings = this.table.fnSettings();
+            var cols = oSettings.aoColumns;
+            var index = this.getColIndex(field,cols);
+            if (index != -1)
+                this.table.fnSetColumnVis(index, true);
+        },
+
+        hide_column: function(field)
+        {
+            var oSettings = this.table.fnSettings();
+            var cols = oSettings.aoColumns;
+            var index = this.getColIndex(field,cols);
+            if (index != -1)
+                this.table.fnSetColumnVis(index, false);
+        },
+
+        set_checkbox: function(record, checked)
+        {
+            /* Default: checked = true */
+            if (typeof checked === 'undefined')
+                checked = true;
+
+            var key_value;
+            /* The function accepts both records and their key */
+            switch (manifold.get_type(record)) {
+                case TYPE_VALUE:
+                    key_value = record;
+                    break;
+                case TYPE_RECORD:
+                    /* XXX Test the key before ? */
+                    key_value = record[this.key];
+                    break;
+                default:
+                    throw "Not implemented";
+                    break;
+            }
+
+
+            var checkbox_id = this.id('checkbox', this.id_from_key(this.key, key_value));
+            checkbox_id = '#' + checkbox_id.replace(/\./g, '\\.');
+
+            var element = $(checkbox_id, this.table.fnGetNodes());
+
+            element.attr('checked', checked);
+        },
+
+        /*************************** QUERY HANDLER ****************************/
+
+        on_filter_added: function(filter)
+        {
+            // XXX
+            this.redraw_table();
+        },
+
+        on_filter_removed: function(filter)
+        {
+            // XXX
+            this.redraw_table();
+        },
+        
+        on_filter_clear: function()
+        {
+            // XXX
+            this.redraw_table();
+        },
+
+        on_field_added: function(field)
+        {
+            this.show_column(field);
+        },
+
+        on_field_removed: function(field)
+        {
+            this.hide_column(field);
+        },
+
+        on_field_clear: function()
+        {
+            alert('Hazelnut::clear_fields() not implemented');
+        },
+
+        /*************************** RECORD HANDLER ***************************/
+
+        on_new_record: function(record)
+        {
+            /* NOTE in fact we are doing a join here */
+            if (this.received_all)
+                // update checkbox for record
+                this.set_checkbox(record, true);
+            else
+                // store for later update of checkboxes
+                this.in_set_buffer.push(record);
+        },
+
+        on_clear_records: function()
+        {
+        },
+
+        // Could be the default in parent
+        on_query_in_progress: function()
+        {
+            this.spin();
+        },
+
+        on_query_done: function()
+        {
+            if (this.received_all)
+                this.unspin();
+            this.received_set = true;
+        },
+        
+        on_field_state_changed: function(data)
+        {
+            switch(data.request) {
+                case FIELD_REQUEST_ADD:
+                case FIELD_REQUEST_ADD_RESET:
+                    this.set_checkbox(data.value, true);
+                    break;
+                case FIELD_REQUEST_REMOVE:
+                case FIELD_REQUEST_REMOVE_RESET:
+                    this.set_checkbox(data.value, false);
+                    break;
+                default:
+                    break;
+            }
+        },
+
+        // all
+
+        on_all_new_record: function(record)
+        {
+            this.new_record(record);
+        },
+
+        on_all_clear_records: function()
+        {
+            this.clear_table();
+
+        },
+
+        on_all_query_in_progress: function()
+        {
+            // XXX parent
+            this.spin();
+        }, // on_all_query_in_progress
+
+        on_all_query_done: function()
+        {
+            var self = this;
+            if (this.received_set) {
+                /* XXX needed ? XXX We uncheck all checkboxes ... */
+                $("[id^='datatables-checkbox-" + this.options.plugin_uuid +"']").attr('checked', false);
+
+                /* ... and check the ones specified in the resource list */
+                $.each(this.in_set_buffer, function(i, record) {
+                    self.set_checkbox(record, true);
+                });
+
+                this.unspin();
+            }
+            this.received_all = true;
+
+        }, // on_all_query_done
+
+        /************************** PRIVATE METHODS ***************************/
+
+        /** 
+         * @brief Hazelnut filtering function
+         */
+        _hazelnut_filter: function(oSettings, aData, iDataIndex)
+        {
+            var cur_query = this.current_query;
+            if (!cur_query) return true;
+            var ret = true;
+
+            /* We have an array of filters : a filter is an array (key op val) 
+             * field names (unless shortcut)    : oSettings.aoColumns  = [ sTitle ]
+             *     can we exploit the data property somewhere ?
+             * field values (unless formatting) : aData
+             *     formatting should leave original data available in a hidden field
+             *
+             * The current line should validate all filters
+             */
+            $.each (cur_query.filters, function(index, filter) { 
+                /* XXX How to manage checkbox ? */
+                var key = filter[0]; 
+                var op = filter[1];
+                var value = filter[2];
+
+                /* Determine index of key in the table columns */
+                var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
+
+                /* Unknown key: no filtering */
+                if (typeof(col) == 'undefined')
+                    return;
+
+                col_value=unfold.get_value(aData[col]);
+                /* Test whether current filter is compatible with the column */
+                if (op == '=' || op == '==') {
+                    if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
+                        ret = false;
+                }else if (op == '!=') {
+                    if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
+                        ret = false;
+                } else if(op=='<') {
+                    if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
+                        ret = false;
+                } else if(op=='>') {
+                    if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
+                        ret = false;
+                } else if(op=='<=' || op=='≤') {
+                    if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
+                        ret = false;
+                } else if(op=='>=' || op=='≥') {
+                    if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
+                        ret = false;
+                }else{
+                    // How to break out of a loop ?
+                    alert("filter not supported");
+                    return false;
+                }
+
+            });
+            return ret;
+        },
+
+        _hazelnut_draw_callback: function()
+        {
+            /* 
+             * Handle clicks on checkboxes: reassociate checkbox click every time
+             * the table is redrawn 
+             */
+            this.elts('hazelnut-checkbox').unbind('click').click(this, this._check_click);
+
+            if (!this.table)
+                return;
+
+            /* Remove pagination if we show only a few results */
+            var wrapper = this.table; //.parent().parent().parent();
+            var rowsPerPage = this.table.fnSettings()._iDisplayLength;
+            var rowsToShow = this.table.fnSettings().fnRecordsDisplay();
+            var minRowsPerPage = this.table.fnSettings().aLengthMenu[0];
+
+            if ( rowsToShow <= rowsPerPage || rowsPerPage == -1 ) {
+                $('.hazelnut_paginate', wrapper).css('visibility', 'hidden');
+            } else {
+                $('.hazelnut_paginate', wrapper).css('visibility', 'visible');
+            }
+
+            if ( rowsToShow <= minRowsPerPage ) {
+                $('.hazelnut_length', wrapper).css('visibility', 'hidden');
+            } else {
+                $('.hazelnut_length', wrapper).css('visibility', 'visible');
+            }
+        },
+
+        _check_click: function(e) 
+        {
+            e.stopPropagation();
+
+            var self = e.data;
+
+            // XXX this.value = key of object to be added... what about multiple keys ?
+            manifold.raise_event(self.options.query_uuid, this.checked?SET_ADD:SET_REMOVED, this.value);
+            //return false; // prevent checkbox to be checked, waiting response from manifold plugin api
+            
+        },
+
+        _selectAll: function() 
+        {
+            // requires jQuery id
+            var uuid=this.id.split("-");
+            var oTable=$("#hazelnut-"+uuid[1]).dataTable();
+            // Function available in Hazelnut 1.9.x
+            // Filter : displayed data only
+            var filterData = oTable._('tr', {"filter":"applied"});   
+            /* TODO: WARNING if too many nodes selected, use filters to reduce nuber of nodes */        
+            if(filterData.length<=100){
+                $.each(filterData, function(index, obj) {
+                    var last=$(obj).last();
+                    var key_value=unfold.get_value(last[0]);
+                    if(typeof($(last[0]).attr('checked'))=="undefined"){
+                        $.publish('selected', 'add/'+key_value);
+                    }
+                });
+            }
+        },
+
+    });
+
+    $.plugin('Hazelnut', Hazelnut);
+
+})(jQuery);
diff --git a/plugins/tabs/static/tabs.html b/plugins/tabs/static/tabs.html
new file mode 100644
index 00000000..298dec92
--- /dev/null
+++ b/plugins/tabs/static/tabs.html
@@ -0,0 +1,12 @@
+<ul class="nav nav-tabs" id='tabs-{{ domid }}'>
+{% for son in sons %}
+<li{% if son.is_active %} class='active'{% endif %}> <a href="#tab-{{ son.domid }}" data-toggle="tab">{{ son.title }}</a> </li>
+{% endfor %}
+</ul><!--nav-tabs-->
+<div class="tab-content">
+{% for son in sons %}
+<div class="tab-pane fade in{% if son.is_active %} active{% endif %}" id="tab-{{ son.domid }}">
+{{ son.rendered }}
+</div><!--tab-pane-->
+{% endfor %}
+</div><!--tab-content-->
diff --git a/plugins/tabs/tabs.py b/plugins/tabs/tabs.py
new file mode 100644
index 00000000..6da94da1
--- /dev/null
+++ b/plugins/tabs/tabs.py
@@ -0,0 +1,19 @@
+from unfold.composite import Composite
+
+class Tabs (Composite):
+    
+    def requirements (self):
+        return { 'js_files'     : ['js/tabs.js', 'js/bootstrap.js'],
+                 'css_files'    : ['css/bootstrap.css', 'css/tabs.css', ] 
+                 }
+
+    def template_file (self):
+        return "tabs.html"
+
+    # see Composite.py for the details of template_env, that exposes global
+    # 'sons' as a list of sons with each a set of a few attributes
+    def json_settings_list (self):
+        return []
+
+    def export_json_settings(self):
+        return True
diff --git a/portal/demo_sliceview.py b/portal/demo_sliceview.py
new file mode 100644
index 00000000..3921e5a0
--- /dev/null
+++ b/portal/demo_sliceview.py
@@ -0,0 +1,289 @@
+# Create your views here.
+
+from django.template                 import RequestContext
+from django.shortcuts                import render_to_response
+#from django.contrib.auth.decorators  import login_required
+#from django.http                     import HttpResponseRedirect
+
+from unfold.loginrequired            import LoginRequiredAutoLogoutView
+
+from unfold.page                     import Page
+from manifold.core.query             import Query, AnalyzedQuery
+from manifold.manifoldresult         import ManifoldException
+from manifold.metadata               import MetaData as Metadata
+#from myslice.viewutils               import quickfilter_criterias, topmenu_items, the_user
+from myslice.viewutils               import topmenu_items, the_user
+
+from plugins.raw.raw                 import Raw
+from plugins.stack.stack             import Stack
+from plugins.tabs.tabs               import Tabs
+from plugins.lists.slicelist         import SliceList
+from plugins.hazelnut           import Hazelnut 
+from plugins.resources_selected      import ResourcesSelected
+from plugins.googlemap               import GoogleMap
+from plugins.senslabmap.senslabmap   import SensLabMap
+from plugins.querycode.querycode     import QueryCode
+from plugins.query_editor            import QueryEditor
+from plugins.active_filters          import ActiveFilters
+from plugins.quickfilter.quickfilter import QuickFilter
+from plugins.messages.messages       import Messages
+#from plugins.updater                 import Updater
+
+tmp_default_slice='ple.upmc.myslicedemo'
+debug = True
+
+class SliceView (LoginRequiredAutoLogoutView):
+
+    def get (self,request, slicename=tmp_default_slice):
+        page = Page(request)
+        page.add_css_files ('css/slice-view.css')
+        page.expose_js_metadata()
+
+        metadata = page.get_metadata()
+        resource_md = metadata.details_by_object('resource')
+        resource_fields = [column['name'] for column in resource_md['column']]
+
+        user_md = metadata.details_by_object('user')
+        user_fields = ['user_hrn'] # [column['name'] for column in user_md['column']]
+
+        # TODO The query to run is embedded in the URL
+        main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
+        main_query.select(
+                'slice_hrn',
+                'resource.hrn', 'resource.hostname', 'resource.type', 'resource.network_hrn',
+                #'lease.urn',
+                'user.user_hrn',
+                #'application.measurement_point.counter'
+        )
+
+        query_resource_all = Query.get('resource').select(resource_fields)
+        query_user_all = Query.get('user').select(user_fields)
+
+        # Temporarily filter users which is slow
+        query_user_all = query_user_all.filter_by('authority_hrn', '==', 'ple.upmc')
+
+        aq = AnalyzedQuery(main_query, metadata=metadata)
+        page.enqueue_query(main_query, analyzed_query=aq)
+        page.enqueue_query(query_resource_all)
+        page.enqueue_query(query_user_all)
+
+        # Prepare the display according to all metadata
+        # (some parts will be pending, others can be triggered by users).
+        # 
+        # For example slice measurements will not be requested by default...
+
+        # Create the base layout (Stack)...
+        main_stack = Stack (
+            page=page,
+            title="Slice %s"%slicename,
+            sons=[],
+        )
+
+        # ... responsible for the slice properties...
+
+
+        main_stack.insert (
+            Raw (page=page,togglable=False, toggled=True,html="<h2> Slice page for %s</h2>"%slicename)
+        )
+
+        main_stack.insert(
+            Raw (page=page,togglable=False, toggled=True,html='<b>Description:</b> TODO')
+        )
+
+        sq_plugin = Tabs (
+            page=page,
+            title="Slice view for %s"%slicename,
+            togglable=False,
+            sons=[],
+        )
+
+
+        # ... and for the relations
+        # XXX Let's hardcode resources for now
+        sq_resource = aq.subquery('resource')
+        sq_user     = aq.subquery('user')
+        sq_lease    = aq.subquery('lease')
+        sq_measurement = aq.subquery('measurement')
+        
+
+        ############################################################################
+        # RESOURCES
+        # 
+        # A stack inserted in the subquery tab that will hold all operations
+        # related to resources
+        # 
+        
+        stack_resources = Stack(
+            page = page,
+            title        = 'Resources',
+            sons=[],
+        )
+
+        resource_query_editor = QueryEditor(
+            page  = page,
+            query = sq_resource,
+        )
+        stack_resources.insert(resource_query_editor)
+
+        resource_active_filters = ActiveFilters(
+            page  = page,
+            query = sq_resource,
+        )
+        stack_resources.insert(resource_active_filters)
+
+        # --------------------------------------------------------------------------
+        # Different displays = DataTables + GoogleMaps
+        #
+        tab_resource_plugins = Tabs(
+            page    = page,
+            sons = []
+        )
+
+        tab_resource_plugins.insert(Hazelnut( 
+            page       = page,
+            title      = 'List',
+            domid      = 'checkboxes',
+            # this is the query at the core of the slice list
+            query      = sq_resource,
+            query_all  = query_resource_all,
+            checkboxes = True,
+            datatables_options = { 
+                # for now we turn off sorting on the checkboxes columns this way
+                # this of course should be automatic in hazelnut
+                'aoColumns'      : [None, None, None, None, {'bSortable': False}],
+                'iDisplayLength' : 25,
+                'bLengthChange'  : True,
+            },
+        ))
+
+        tab_resource_plugins.insert(GoogleMap(
+            page       = page,
+            title      = 'Geographic view',
+            domid      = 'gmap',
+            # tab's sons preferably turn this off
+            togglable  = False,
+            query      = sq_resource,
+            query_all  = query_resource_all,
+            checkboxes = True,
+            # center on Paris
+            latitude   = 49.,
+            longitude  = 2.2,
+            zoom       = 3,
+        ))
+
+        stack_resources.insert(tab_resource_plugins)
+
+        sq_plugin.insert(stack_resources)
+
+        ############################################################################
+        # USERS
+        # 
+
+        tab_users = Tabs(
+            page         = page,
+            title        = 'Users',
+            domid        = 'thetabs2',
+            # activeid   = 'checkboxes',
+            active_domid = 'checkboxes2',
+        )
+        sq_plugin.insert(tab_users)
+
+        tab_users.insert(Hazelnut( 
+            page        = page,
+            title       = 'List',
+            domid       = 'checkboxes2',
+            # tab's sons preferably turn this off
+            togglable   = False,
+            # this is the query at the core of the slice list
+            query       = sq_user,
+            query_all  = query_user_all,
+            checkboxes  = True,
+            datatables_options = { 
+                # for now we turn off sorting on the checkboxes columns this way
+                # this of course should be automatic in hazelnut
+                'aoColumns'      : [None, None, None, None, {'bSortable': False}],
+                'iDisplayLength' : 25,
+                'bLengthChange'  : True,
+            },
+        ))
+
+        tab_measurements = Tabs (
+            page         = page,
+            title        = 'Measurements',
+            domid        = 'thetabs3',
+            # activeid   = 'checkboxes',
+            active_domid = 'checkboxes3',
+        )
+        sq_plugin.insert(tab_measurements)
+
+        tab_measurements.insert(Hazelnut( 
+            page        = page,
+            title       = 'List',
+            domid       = 'checkboxes3',
+            # tab's sons preferably turn this off
+            togglable   = False,
+            # this is the query at the core of the slice list
+            query       = sq_measurement,
+            checkboxes  = True,
+            datatables_options = { 
+                # for now we turn off sorting on the checkboxes columns this way
+                # this of course should be automatic in hazelnut
+                'aoColumns'      : [None, None, None, None, {'bSortable': False}],
+                'iDisplayLength' : 25,
+                'bLengthChange'  : True,
+            },
+        ))
+
+        main_stack.insert(sq_plugin)
+
+        # --------------------------------------------------------------------------
+        # ResourcesSelected
+        #
+        main_stack.insert(ResourcesSelected(
+            page                = page,
+            title               = 'Pending operations',
+            query               = main_query,
+            togglable           = True,
+        ))
+
+        main_stack.insert(Messages(
+            page   = page,
+            title  = "Runtime messages for slice %s"%slicename,
+            domid  = "msgs-pre",
+            levels = "ALL",
+        ))
+    #    main_stack.insert(Updater(
+    #        page   = page,
+    #        title  = "wont show up as non togglable by default",
+    #        query  = main_query,
+    #        label  = "Update slice",
+    #    ))
+        
+
+
+        # variables that will get passed to the view-unfold1.html template
+        template_env = {}
+        
+        # define 'unfold1_main' to the template engine - the main contents
+        template_env [ 'unfold1_main' ] = main_stack.render(request)
+
+        # more general variables expected in the template
+        template_env [ 'title' ] = 'Test view that combines various plugins'
+        # the menu items on the top
+        template_env [ 'topmenu_items' ] = topmenu_items('Slice', request) 
+        # so we can sho who is logged
+        template_env [ 'username' ] = the_user (request) 
+
+        # don't forget to run the requests
+        page.expose_queries ()
+
+        # xxx create another plugin with the same query and a different layout (with_datatables)
+        # show that it worls as expected, one single api call to backend and 2 refreshed views
+
+        # the prelude object in page contains a summary of the requirements() for all plugins
+        # define {js,css}_{files,chunks}
+        prelude_env = page.prelude_env()
+        template_env.update(prelude_env)
+        result=render_to_response ('view-unfold1.html',template_env,
+                                   context_instance=RequestContext(request))
+        return result
diff --git a/third-party/datatables-1.9.4/js/datatables-bs3.js b/third-party/datatables-1.9.4/js/datatables-bs3.js
new file mode 100644
index 00000000..6c9f146f
--- /dev/null
+++ b/third-party/datatables-1.9.4/js/datatables-bs3.js
@@ -0,0 +1,383 @@
+/* Set the defaults for DataTables initialisation */
+$.extend( true, $.fn.dataTable.defaults, {
+	"sDom": "<'row'<'col-sm-12'<'pull-right'f><'pull-left'l>r<'clearfix'>>>t<'row'<'col-sm-12'<'pull-left'i><'pull-right'p><'clearfix'>>>",
+    "sPaginationType": "bs_normal",
+    "oLanguage": {
+        "sLengthMenu": "Show _MENU_ Rows",
+        "sSearch": ""
+    }
+} );
+
+/* Default class modification */
+$.extend( $.fn.dataTableExt.oStdClasses, {
+	"sWrapper": "dataTables_wrapper form-inline"
+} );
+
+/* API method to get paging information */
+$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
+{
+	return {
+		"iStart":         oSettings._iDisplayStart,
+		"iEnd":           oSettings.fnDisplayEnd(),
+		"iLength":        oSettings._iDisplayLength,
+		"iTotal":         oSettings.fnRecordsTotal(),
+		"iFilteredTotal": oSettings.fnRecordsDisplay(),
+		"iPage":          oSettings._iDisplayLength === -1 ?
+			0 : Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
+		"iTotalPages":    oSettings._iDisplayLength === -1 ?
+			0 : Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
+	};
+};
+
+/* Bootstrap style pagination control */
+$.extend( $.fn.dataTableExt.oPagination, {
+	"bs_normal": {
+		"fnInit": function( oSettings, nPaging, fnDraw ) {
+			var oLang = oSettings.oLanguage.oPaginate;
+			var fnClickHandler = function ( e ) {
+				e.preventDefault();
+				if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
+					fnDraw( oSettings );
+				}
+			};
+			$(nPaging).append(
+				'<ul class="pagination">'+
+					'<li class="prev disabled"><a href="#"><span class="glyphicon glyphicon-chevron-left"></span>&nbsp;'+oLang.sPrevious+'</a></li>'+
+					'<li class="next disabled"><a href="#">'+oLang.sNext+'&nbsp;<span class="glyphicon glyphicon-chevron-right"></span></a></li>'+
+				'</ul>'
+			);
+			var els = $('a', nPaging);
+			$(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
+			$(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
+		},
+		"fnUpdate": function ( oSettings, fnDraw ) {
+			var iListLength = 5;
+			var oPaging = oSettings.oInstance.fnPagingInfo();
+			var an = oSettings.aanFeatures.p;
+			var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);
+			if ( oPaging.iTotalPages < iListLength) {
+				iStart = 1;
+				iEnd = oPaging.iTotalPages;
+			}
+			else if ( oPaging.iPage <= iHalf ) {
+				iStart = 1;
+				iEnd = iListLength;
+			} else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
+				iStart = oPaging.iTotalPages - iListLength + 1;
+				iEnd = oPaging.iTotalPages;
+			} else {
+				iStart = oPaging.iPage - iHalf + 1;
+				iEnd = iStart + iListLength - 1;
+			}
+			for ( i=0, ien=an.length ; i<ien ; i++ ) {
+				$('li:gt(0)', an[i]).filter(':not(:last)').remove();
+				for ( j=iStart ; j<=iEnd ; j++ ) {
+					sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
+					$('<li '+sClass+'><a href="#">'+j+'</a></li>')
+						.insertBefore( $('li:last', an[i])[0] )
+						.bind('click', function (e) {
+							e.preventDefault();
+							oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
+							fnDraw( oSettings );
+						} );
+				}
+				if ( oPaging.iPage === 0 ) {
+					$('li:first', an[i]).addClass('disabled');
+				} else {
+					$('li:first', an[i]).removeClass('disabled');
+				}
+
+				if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
+					$('li:last', an[i]).addClass('disabled');
+				} else {
+					$('li:last', an[i]).removeClass('disabled');
+				}
+			}
+		}
+	},	
+	"bs_two_button": {
+		"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
+		{
+			var oLang = oSettings.oLanguage.oPaginate;
+			var oClasses = oSettings.oClasses;
+			var fnClickHandler = function ( e ) {
+				if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
+				{
+					fnCallbackDraw( oSettings );
+				}
+			};
+			var sAppend = '<ul class="pagination">'+
+				'<li class="prev"><a class="'+oSettings.oClasses.sPagePrevDisabled+'" tabindex="'+oSettings.iTabIndex+'" role="button"><span class="glyphicon glyphicon-chevron-left"></span>&nbsp;'+oLang.sPrevious+'</a></li>'+
+				'<li class="next"><a class="'+oSettings.oClasses.sPageNextDisabled+'" tabindex="'+oSettings.iTabIndex+'" role="button">'+oLang.sNext+'&nbsp;<span class="glyphicon glyphicon-chevron-right"></span></a></li>'+
+				'</ul>';
+			$(nPaging).append( sAppend );
+			var els = $('a', nPaging);
+			var nPrevious = els[0],
+				nNext = els[1];
+			oSettings.oApi._fnBindAction( nPrevious, {action: "previous"}, fnClickHandler );
+			oSettings.oApi._fnBindAction( nNext,     {action: "next"},     fnClickHandler );
+			if ( !oSettings.aanFeatures.p )
+			{
+				nPaging.id = oSettings.sTableId+'_paginate';
+				nPrevious.id = oSettings.sTableId+'_previous';
+				nNext.id = oSettings.sTableId+'_next';
+				nPrevious.setAttribute('aria-controls', oSettings.sTableId);
+				nNext.setAttribute('aria-controls', oSettings.sTableId);
+			}
+		},
+		"fnUpdate": function ( oSettings, fnCallbackDraw )
+		{
+			if ( !oSettings.aanFeatures.p )
+			{
+				return;
+			}
+			var oPaging = oSettings.oInstance.fnPagingInfo();
+			var oClasses = oSettings.oClasses;
+			var an = oSettings.aanFeatures.p;
+			var nNode;
+			for ( var i=0, iLen=an.length ; i<iLen ; i++ )
+			{
+				if ( oPaging.iPage === 0 ) {
+					$('li:first', an[i]).addClass('disabled');
+				} else {
+					$('li:first', an[i]).removeClass('disabled');
+				}
+
+				if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
+					$('li:last', an[i]).addClass('disabled');
+				} else {
+					$('li:last', an[i]).removeClass('disabled');
+				}
+			}
+		}
+	},
+	"bs_four_button": {
+		"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
+			{
+				var oLang = oSettings.oLanguage.oPaginate;
+				var oClasses = oSettings.oClasses;
+				var fnClickHandler = function ( e ) {
+					if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
+					{
+						fnCallbackDraw( oSettings );
+					}
+				};
+				$(nPaging).append(
+					'<ul class="pagination">'+
+					'<li class="disabled"><a  tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageFirst+'"><span class="glyphicon glyphicon-backward"></span>&nbsp;'+oLang.sFirst+'</a></li>'+
+					'<li class="disabled"><a  tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPagePrevious+'"><span class="glyphicon glyphicon-chevron-left"></span>&nbsp;'+oLang.sPrevious+'</a></li>'+
+					'<li><a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageNext+'">'+oLang.sNext+'&nbsp;<span class="glyphicon glyphicon-chevron-right"></span></a></li>'+
+					'<li><a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageLast+'">'+oLang.sLast+'&nbsp;<span class="glyphicon glyphicon-forward"></span></a></li>'+
+					'</ul>'
+				);
+				var els = $('a', nPaging);
+				var nFirst = els[0],
+					nPrev = els[1],
+					nNext = els[2],
+					nLast = els[3];
+				oSettings.oApi._fnBindAction( nFirst, {action: "first"},    fnClickHandler );
+				oSettings.oApi._fnBindAction( nPrev,  {action: "previous"}, fnClickHandler );
+				oSettings.oApi._fnBindAction( nNext,  {action: "next"},     fnClickHandler );
+				oSettings.oApi._fnBindAction( nLast,  {action: "last"},     fnClickHandler );
+				if ( !oSettings.aanFeatures.p )
+				{
+					nPaging.id = oSettings.sTableId+'_paginate';
+					nFirst.id =oSettings.sTableId+'_first';
+					nPrev.id =oSettings.sTableId+'_previous';
+					nNext.id =oSettings.sTableId+'_next';
+					nLast.id =oSettings.sTableId+'_last';
+				}
+			},
+		"fnUpdate": function ( oSettings, fnCallbackDraw )
+			{
+				if ( !oSettings.aanFeatures.p )
+				{
+					return;
+				}
+				var oPaging = oSettings.oInstance.fnPagingInfo();
+				var oClasses = oSettings.oClasses;
+				var an = oSettings.aanFeatures.p;
+				var nNode;
+				for ( var i=0, iLen=an.length ; i<iLen ; i++ )
+				{
+					if ( oPaging.iPage === 0 ) {
+						$('li:eq(0)', an[i]).addClass('disabled');
+						$('li:eq(1)', an[i]).addClass('disabled');
+					} else {
+						$('li:eq(0)', an[i]).removeClass('disabled');
+						$('li:eq(1)', an[i]).removeClass('disabled');
+					}
+
+					if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
+						$('li:eq(2)', an[i]).addClass('disabled');
+						$('li:eq(3)', an[i]).addClass('disabled');
+					} else {
+						$('li:eq(2)', an[i]).removeClass('disabled');
+						$('li:eq(3)', an[i]).removeClass('disabled');
+					}
+				}
+			}
+	},
+	"bs_full": {
+		"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
+			{
+				var oLang = oSettings.oLanguage.oPaginate;
+				var oClasses = oSettings.oClasses;
+				var fnClickHandler = function ( e ) {
+					if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
+					{
+						fnCallbackDraw( oSettings );
+					}
+				};
+				$(nPaging).append(
+					'<ul class="pagination">'+
+					'<li class="disabled"><a  tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageFirst+'">'+oLang.sFirst+'</a></li>'+
+					'<li class="disabled"><a  tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPagePrevious+'">'+oLang.sPrevious+'</a></li>'+
+					'<li><a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageNext+'">'+oLang.sNext+'</a></li>'+
+					'<li><a tabindex="'+oSettings.iTabIndex+'" class="'+oClasses.sPageButton+" "+oClasses.sPageLast+'">'+oLang.sLast+'</a></li>'+
+					'</ul>'
+				);
+				var els = $('a', nPaging);
+				var nFirst = els[0],
+					nPrev = els[1],
+					nNext = els[2],
+					nLast = els[3];
+				oSettings.oApi._fnBindAction( nFirst, {action: "first"},    fnClickHandler );
+				oSettings.oApi._fnBindAction( nPrev,  {action: "previous"}, fnClickHandler );
+				oSettings.oApi._fnBindAction( nNext,  {action: "next"},     fnClickHandler );
+				oSettings.oApi._fnBindAction( nLast,  {action: "last"},     fnClickHandler );
+				if ( !oSettings.aanFeatures.p )
+				{
+					nPaging.id = oSettings.sTableId+'_paginate';
+					nFirst.id =oSettings.sTableId+'_first';
+					nPrev.id =oSettings.sTableId+'_previous';
+					nNext.id =oSettings.sTableId+'_next';
+					nLast.id =oSettings.sTableId+'_last';
+				}
+			},
+		"fnUpdate": function ( oSettings, fnCallbackDraw )
+			{
+				if ( !oSettings.aanFeatures.p )
+				{
+					return;
+				}
+				var oPaging = oSettings.oInstance.fnPagingInfo();
+				var iPageCount = $.fn.dataTableExt.oPagination.iFullNumbersShowPages;
+				var iPageCountHalf = Math.floor(iPageCount / 2);
+				var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
+				var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
+				var sList = "";
+				var iStartButton, iEndButton, i, iLen;
+				var oClasses = oSettings.oClasses;
+				var anButtons, anStatic, nPaginateList, nNode;
+				var an = oSettings.aanFeatures.p;
+				var fnBind = function (j) {
+					oSettings.oApi._fnBindAction( this, {"page": j+iStartButton-1}, function(e) {
+						oSettings.oApi._fnPageChange( oSettings, e.data.page );
+						fnCallbackDraw( oSettings );
+						e.preventDefault();
+					} );
+				};
+				if ( oSettings._iDisplayLength === -1 )
+				{
+					iStartButton = 1;
+					iEndButton = 1;
+					iCurrentPage = 1;
+				}
+				else if (iPages < iPageCount)
+				{
+					iStartButton = 1;
+					iEndButton = iPages;
+				}
+				else if (iCurrentPage <= iPageCountHalf)
+				{
+					iStartButton = 1;
+					iEndButton = iPageCount;
+				}
+				else if (iCurrentPage >= (iPages - iPageCountHalf))
+				{
+					iStartButton = iPages - iPageCount + 1;
+					iEndButton = iPages;
+				}
+				else
+				{
+					iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
+					iEndButton = iStartButton + iPageCount - 1;
+				}
+				for ( i=iStartButton ; i<=iEndButton ; i++ )
+				{
+					sList += (iCurrentPage !== i) ?
+						'<li><a tabindex="'+oSettings.iTabIndex+'">'+oSettings.fnFormatNumber(i)+'</a></li>' :
+						'<li class="active"><a tabindex="'+oSettings.iTabIndex+'">'+oSettings.fnFormatNumber(i)+'</a></li>';
+				}
+				for ( i=0, iLen=an.length ; i<iLen ; i++ )
+				{
+					nNode = an[i];
+					if ( !nNode.hasChildNodes() )
+					{
+						continue;
+					}
+					$('li:gt(1)', an[i]).filter(':not(li:eq(-2))').filter(':not(li:eq(-1))').remove();
+					if ( oPaging.iPage === 0 ) {
+						$('li:eq(0)', an[i]).addClass('disabled');
+						$('li:eq(1)', an[i]).addClass('disabled');
+					} else {
+						$('li:eq(0)', an[i]).removeClass('disabled');
+						$('li:eq(1)', an[i]).removeClass('disabled');
+					}
+					if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
+						$('li:eq(-1)', an[i]).addClass('disabled');
+						$('li:eq(-2)', an[i]).addClass('disabled');
+					} else {
+						$('li:eq(-1)', an[i]).removeClass('disabled');
+						$('li:eq(-2)', an[i]).removeClass('disabled');
+					}
+					$(sList)
+						.insertBefore('li:eq(-2)', an[i])
+						.bind('click', function (e) {
+							e.preventDefault();
+							oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
+							fnCallbackDraw( oSettings );
+						});
+				}
+			}
+	}	
+} );
+
+
+/*
+ * TableTools Bootstrap compatibility
+ * Required TableTools 2.1+
+ */
+if ( $.fn.DataTable.TableTools ) {
+	// Set the classes that TableTools uses to something suitable for Bootstrap
+	$.extend( true, $.fn.DataTable.TableTools.classes, {
+		"container": "DTTT btn-group",
+		"buttons": {
+			"normal": "btn",
+			"disabled": "disabled"
+		},
+		"collection": {
+			"container": "DTTT_dropdown dropdown-menu",
+			"buttons": {
+				"normal": "",
+				"disabled": "disabled"
+			}
+		},
+		"print": {
+			"info": "DTTT_print_info modal"
+		},
+		"select": {
+			"row": "active"
+		}
+	} );
+
+	// Have the collection use a bootstrap compatible dropdown
+	$.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, {
+		"collection": {
+			"container": "ul",
+			"button": "li",
+			"liner": "a"
+		}
+	} );
+}
-- 
2.47.0