Commit f4fc5d33 authored by Alain Leufroy's avatar Alain Leufroy
Browse files

implement a 'slickgrid' view that inherite from cw 'table' view

This view is very simple (no pagination, no column ordering).
Although it allows other table's view cutomization (headers, cellvids,
etc.).

The idea of this code is to override table view's code in order to
build grid content instead of writing HTML, then call the javascript
SlikGrid constructor.
parent 381a7c0f061c
Summary
-------
=======
Table view rendered using the SlickGrid_ javascript librairy.
SlickGrid is an advanced JavaScript grid/spreadsheet component.
This view accepts any non-empty rset. It uses intropection on the
result set to compute column names and the proper way to display the
cells.
It is highly configuration and accepts the same wealth of option than
cubicweb.web.view.tableview.RsetTableView.
.. _SlickGrid: https://github.com/mleibman/SlickGrid
Exapmple
========
To try it at the speed of light
--------------------------------
Once your instance is running you can go to::
http://localhost:8080/view?rql=Any L, X WHERE X is CWUser, X login L&vid=slickgrid
That's all. The rendered table uses the 'slickgrid' view.
Calling the slidgrid from your views
------------------------------------
The simplest way is to simply call::
.. sourcecode:: python
self._cw.wview('slickgrid', rset, 'null')
Although, because it inherite from cubicweb `table` view, options can
be specified at selection time:
* `displaycols`, if not `None`, should be a list of rset's columns to be
displayed.
* `headers`, if not `None`, should be a list of headers for the table's
columns. `None` values in the list will be replaced by computed column
names.
* `cellvids`, if not `None`, should be a dictionary with table column index
as key and a view identifier as value, telling the view that should be
used in the given column.
So one can uses::
self._cw.wview('slickgrid', rset, 'null',
headers=(_('first'), _('second'), _('third')),
cellvids={0: 'text', 1: 'inline', 2:'outofcontext'})
Note that pagination is not working for now.
/*-*- coding: utf-8 -*- */
/*copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. */
/*contact http://www.logilab.fr -- mailto:contact@logilab.fr */
/* */
/*This program is free software: you can redistribute it and/or modify it */
/* under the terms of the GNU Lesser General Public License as published */
/*by the Free Software Foundation, either version 2.1 of the License, or */
/* (at your option)any later version. */
/* */
/*This program is distributed in the hope that it will be useful, but */
/*WITHOUT ANY WARRANTY; without even the implied warranty of */
/*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser */
/* General Public License for more details. */
/* */
/*You should have received a copy of the GNU Lesser General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
[id^="slickgrid-coverage-"] {
background: #eeeeee;
border: 1px dotted #aaaaaa !important;
}
[class^="slickgrid_"], [class^="slickgrid_"] div{
-webkit-box-sizing: content-box !important;
-moz-box-sizing: content-box !important;
box-sizing: content-box !important;
}
.slick-cell a{
color: #428bca;
}
.slick-columnpicker input{
height: 15px;
font-size: 10px;
}
.slick-columnpicker label{
font-weight: unset;
}
\ No newline at end of file
/*
IMPORTANT:
In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
classes should alter those!
*/
.slick-header-columns {
background: url('images/header-columns-bg.gif') repeat-x center bottom;
border-bottom: 1px solid silver;
}
.slick-header-column {
background: url('images/header-columns-bg.gif') repeat-x center bottom;
border-right: 1px solid silver;
}
.slick-header-column:hover, .slick-header-column-active {
background: white url('images/header-columns-over-bg.gif') repeat-x center bottom;
}
.slick-headerrow {
background: #fafafa;
}
.slick-headerrow-column {
background: #fafafa;
border-bottom: 0;
height: 100%;
}
.slick-row.ui-state-active {
background: #F5F7D7;
}
.slick-row {
position: absolute;
background: white;
border: 0px;
line-height: 20px;
}
.slick-row.selected {
z-index: 10;
background: #DFE8F6;
}
.slick-cell {
padding-left: 4px;
padding-right: 4px;
}
.slick-group {
border-bottom: 2px solid silver;
}
.slick-group-toggle {
width: 9px;
height: 9px;
margin-right: 5px;
}
.slick-group-toggle.expanded {
background: url(images/collapse.gif) no-repeat center center;
}
.slick-group-toggle.collapsed {
background: url(images/expand.gif) no-repeat center center;
}
.slick-group-totals {
color: gray;
background: white;
}
.slick-cell.selected {
background-color: beige;
}
.slick-cell.active {
border-color: gray;
border-style: solid;
}
.slick-sortable-placeholder {
background: silver !important;
}
.slick-row.odd {
background: #fafafa;
}
.slick-row.ui-state-active {
background: #F5F7D7;
}
.slick-row.loading {
opacity: 0.5;
filter: alpha(opacity = 50);
}
.slick-cell.invalid {
border-color: red;
-moz-animation-duration: 0.2s;
-webkit-animation-duration: 0.2s;
-moz-animation-name: slickgrid-invalid-hilite;
-webkit-animation-name: slickgrid-invalid-hilite;
}
@-moz-keyframes slickgrid-invalid-hilite {
from { box-shadow: 0 0 6px red; }
to { box-shadow: none; }
}
@-webkit-keyframes slickgrid-invalid-hilite {
from { box-shadow: 0 0 6px red; }
to { box-shadow: none; }
}
\ No newline at end of file
/*
IMPORTANT:
In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
classes should alter those!
*/
.slick-header.ui-state-default, .slick-headerrow.ui-state-default {
width: 100%;
overflow: hidden;
border-left: 0px;
}
.slick-header-columns, .slick-headerrow-columns {
position: relative;
white-space: nowrap;
cursor: default;
overflow: hidden;
}
.slick-header-column.ui-state-default {
position: relative;
display: inline-block;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
height: 16px;
line-height: 16px;
margin: 0;
padding: 4px;
border-right: 1px solid silver;
border-left: 0px;
border-top: 0px;
border-bottom: 0px;
float: left;
}
.slick-headerrow-column.ui-state-default {
padding: 4px;
}
.slick-header-column-sorted {
font-style: italic;
}
.slick-sort-indicator {
display: inline-block;
width: 8px;
height: 5px;
margin-left: 4px;
margin-top: 6px;
float: left;
}
.slick-sort-indicator-desc {
background: url(images/sort-desc.gif);
}
.slick-sort-indicator-asc {
background: url(images/sort-asc.gif);
}
.slick-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
cursor: col-resize;
width: 4px;
right: 0px;
top: 0;
height: 100%;
}
.slick-sortable-placeholder {
background: silver;
}
.grid-canvas {
position: relative;
outline: 0;
}
.slick-row.ui-widget-content, .slick-row.ui-state-active {
position: absolute;
border: 0px;
width: 100%;
}
.slick-cell, .slick-headerrow-column {
position: absolute;
border: 1px solid transparent;
border-right: 1px dotted silver;
border-bottom-color: silver;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
vertical-align: middle;
z-index: 1;
padding: 1px 2px 2px 1px;
margin: 0;
white-space: nowrap;
cursor: default;
}
.slick-group {
}
.slick-group-toggle {
display: inline-block;
}
.slick-cell.highlighted {
background: lightskyblue;
background: rgba(0, 0, 255, 0.2);
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
-o-transition: all 0.5s;
transition: all 0.5s;
}
.slick-cell.flashing {
border: 1px solid red !important;
}
.slick-cell.editable {
z-index: 11;
overflow: visible;
background: white;
border-color: black;
border-style: solid;
}
.slick-cell:focus {
outline: none;
}
.slick-reorder-proxy {
display: inline-block;
background: blue;
opacity: 0.15;
filter: alpha(opacity = 15);
cursor: move;
}
.slick-reorder-guide {
display: inline-block;
height: 2px;
background: blue;
opacity: 0.7;
filter: alpha(opacity = 70);
}
.slick-selection {
z-index: 10;
position: absolute;
border: 2px dashed black;
}
//-*- coding: utf-8 -*-
//copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
//ontact http://www.logilab.fr -- mailto:contact@logilab.fr
//This program is free software: you can redistribute it and/or modify it under
//the terms of the GNU Lesser General Public License as published by the Free
//Software Foundation, either version 2.1 of the License, or (at your option)
//any later version.
//
//This program is distributed in the hope that it will be useful, but WITHOUT
//ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
//FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
//details.
//
//You should have received a copy of the GNU Lesser General Public License along
//with this program. If not, see <http://www.gnu.org/licenses/>.
cw.slickgrid = new Namespace('cw.slickgrid');
jQuery.extend(cw.slickgrid, {
// Display the grid on the DOM.
// * `nodeSelector`: an JQuery node selector, ex: "#mynode", see
// * `columns`: an object containing columns definitions/options, see
// https://github.com/mleibman/SlickGrid/wiki/Column-Options
// * `data`: an object container the data
displayGrid: function (nodeSelector, columns, data) {
// object containing the grid options
// https://github.com/mleibman/SlickGrid/wiki/Grid-Options
var options = {
forceFitColumns: true,
defaultFormatter: function (r,c,v,cc,d){return v;},
fullWidthRows: true,
autoHeight: true,
syncColumnCellResize: true
};
new Slick.Grid(nodeSelector, data, columns, options);
}
});
/*!
* jquery.event.drag - v 2.2
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
* Open Source MIT License - http://threedubmedia.com/code/license
*/
// Created: 2008-06-04
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x
;(function( $ ){
// add the jquery instance method
$.fn.drag = function( str, arg, opts ){
// figure out the event type
var type = typeof str == "string" ? str : "",
// figure out the event handler...
fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
// fix the event type
if ( type.indexOf("drag") !== 0 )
type = "drag"+ type;
// were options passed
opts = ( str == fn ? arg : opts ) || {};
// trigger or bind event handler
return fn ? this.bind( type, opts, fn ) : this.trigger( type );
};
// local refs (increase compression)
var $event = $.event,
$special = $event.special,
// configure the drag special event
drag = $special.drag = {
// these are the default settings
defaults: {
which: 1, // mouse button pressed to start drag sequence
distance: 0, // distance dragged before dragstart
not: ':input', // selector to suppress dragging on target elements
handle: null, // selector to match handle target elements
relative: false, // true to use "position", false to use "offset"
drop: true, // false to suppress drop events, true or selector to allow
click: false // false to suppress click events after dragend (no proxy)
},
// the key name for stored drag data
datakey: "dragdata",
// prevent bubbling for better performance
noBubble: true,
// count bound related events
add: function( obj ){
// read the interaction data
var data = $.data( this, drag.datakey ),
// read any passed options
opts = obj.data || {};
// count another realted event
data.related += 1;
// extend data options bound with this event
// don't iterate "opts" in case it is a node
$.each( drag.defaults, function( key, def ){
if ( opts[ key ] !== undefined )
data[ key ] = opts[ key ];
});
},
// forget unbound related events
remove: function(){
$.data( this, drag.datakey ).related -= 1;
},
// configure interaction, capture settings
setup: function(){
// check for related events
if ( $.data( this, drag.datakey ) )
return;
// initialize the drag data with copied defaults
var data = $.extend({ related:0 }, drag.defaults );
// store the interaction data
$.data( this, drag.datakey, data );
// bind the mousedown event, which starts drag interactions
$event.add( this, "touchstart mousedown", drag.init, data );
// prevent image dragging in IE...
if ( this.attachEvent )
this.attachEvent("ondragstart", drag.dontstart );
},
// destroy configured interaction
teardown: function(){
var data = $.data( this, drag.datakey ) || {};
// check for related events
if ( data.related )
return;
// remove the stored data
$.removeData( this, drag.datakey );
// remove the mousedown event
$event.remove( this, "touchstart mousedown", drag.init );
// enable text selection
drag.textselect( true );
// un-prevent image dragging in IE...
if ( this.detachEvent )
this.detachEvent("ondragstart", drag.dontstart );
},
// initialize the interaction
init: function( event ){
// sorry, only one touch at a time
if ( drag.touched )
return;
// the drag/drop interaction data
var dd = event.data, results;
// check the which directive
if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
return;
// check for suppressed selector
if ( $( event.target ).is( dd.not ) )
return;
// check for handle selector
if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
return;
drag.touched = event.type == 'touchstart' ? this : null;
dd.propagates = 1;
dd.mousedown = this;
dd.interactions = [ drag.interaction( this, dd ) ];
dd.target = event.target;
dd.pageX = event.pageX;
dd.pageY = event.pageY;
dd.dragging = null;
// handle draginit event...
results = drag.hijack( event, "draginit", dd );
// early cancel
if ( !dd.propagates )
return;
// flatten the result set
results = drag.flatten( results );
// insert new interaction elements
if ( results && results.length ){
dd.interactions = [];
$.each( results, function(){
dd.interactions.push( drag.interaction( this, dd ) );
});
}
// remember how many interactions are propagating
dd.propagates = dd.interactions.length;
// locate and init the drop targets
if ( dd.drop !== false && $special.drop )
$special.drop.handler( event, dd );
// disable text selection
drag.textselect( false );
// bind additional events...
if ( drag.touched )
$event.add( drag.touched, "touchmove touchend", drag.handler, dd );
else
$event.add( document, "mousemove mouseup", drag.handler, dd );
// helps prevent text selection or scrolling
if ( !drag.touched || dd.live )
return false;
},
// returns an interaction object
interaction: function( elem, dd ){
var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
return {
drag: elem,
callback: new drag.callback(),
droppable: [],
offset: offset
};
},
// handle drag-releatd DOM events
handler: function( event ){
// read the data before hijacking anything
var dd = event.data;
// handle various events
switch ( event.type ){
// mousemove, check distance, start dragging
case !dd.dragging && 'touchmove':
event.preventDefault();
case !dd.dragging && 'mousemove':
// drag tolerance, x≤ + y≤ = distance≤
if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
break; // distance tolerance not reached
event.target = dd.target; // force target from "mousedown" event (fix distance issue)
drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
if ( dd.propagates ) // "dragstart" not rejected
dd.dragging = true; // activate interaction
// mousemove, dragging
case 'touchmove':
event.preventDefault();
case 'mousemove':
if ( dd.dragging ){
// trigger "drag"
drag.hijack( event, "drag", dd );
if ( dd.propagates ){
// manage drop events
if ( dd.drop !== false && $special.drop )