Tricky CSS bordering on grid/dashboard layout - javascript

I'm trying to accomplish some tricky borders around certain elements and am on the fence about the best way to accomplish.
Essentially, I want to create a border to the right of each dashboard element and if there is an element under it add a border below it too. I'm using the gridster jquery plugin for the layout engine. It uses <ul><li></li></ul> structure for each grid item.
So given the following I want to the apply the | and _ to each item.
[item] | [item] | [item]
______ _______
[item] | [item]
_____ ______
[ item ]
the issue is these items are 100% customizable by end-users from a column and row spanning aspect. Additionally, the screen resize can come into account to break them down.
I've got a basic implementation for the right with the last item not having a border via css :last.
Any ideas on what would be the best way to accomplish this?

As the grid uses absolute positioning, we can't really use the sequence of the elements in the DOM for much, which makes a pure CSS solution impossible (as far as I can tell...). I would go with some dynamic CSS based on the dimensions of the grid after tthe user has finished dragging a widget. Something like this:
function getGridDimens(grid){
var dimens = {rows: 0, cols: 0};
$.each(grid, function(idx, value){
if(value.row > dimens.rows) dimens.rows = value.row;
if(value.col > dimens.cols) dimens.cols = value.row;
});
return dimens;
}
var $hasRightBorder;
var $hasBottomBorder;
var gridster = $('#gridster ul').gridster({
/* Your options here */
draggable: {
stop: function(e, ui, $widget){
if($hasRightBorder)
$hasRightBorder.css('border-right', '');
if($hasBottomBorder)
$hasBottomBorder.css('border-bottom', '');
var dimens = getGridDimens(gridster);
$hasRightBorder = $('#gridster ul li').not('[data-row=' + dimens.rows + ']');
$hasBottomBorder = $('#gridster ul li').not('[data-col=' + dimens.cols + ']');
$hasRightBorder.css('border-right', '1px solid #eee');
$hasBottomBorder.css('border-bottom', '1px solid #eee');
}
}
});
I don't have a handy gridster example to test this on, but hopefully you get the general idea, and can adapt it to your needs.
Edit re. comments regarding classes instead of raw css:
function getGridDimens(grid){
var dimens = {rows: 0, cols: 0};
$.each(grid, function(idx, value){
if(value.row > dimens.rows) dimens.rows = value.row;
if(value.col > dimens.cols) dimens.cols = value.row;
});
return dimens;
}
var gridster = $('#gridster ul').gridster({
/* Your options here */
draggable: {
stop: function(e, ui, $widget){
$('.hasRightBorder').removeClass('hasRightBorder');
$('.hasBottomBorder').removeClass('hasBottomBorder');
var dimens = getGridDimens(gridster);
$('#gridster ul li').not('[data-row=' + dimens.rows + ']').addClass('hasRightBorder');
$('#gridster ul li').not('[data-col=' + dimens.cols + ']').addClass('hasBottomBorder');
}
}
});

Related

How to filter a very large bootstrap table using pure Javascript

I've built a large table in bootstrap, about 5,000 rows x 10 columns, and I need to filter the table for specific attributes, fast, using only JavaScript. The table has both an id column and an attribute column, i.e.
id | attr | ...
---------------
2 | X | ...
3 | Y | ...
4 | X | ...
To make the filtering process fast, I built a hashtable table that maps the attributes back to the column ids. So for example, I have a mapping:
getRowIds["X"] = [2,4]
The user can enter the attribute "X" in a search box, the hashtable then looks up the corresponding rows that contain "X" (2 and 4 in this case), and then calls the following functions via a map operation:
this.hideRow = function(id) {
document.getElementById(id).style.display="none"
}
this.showRow = function(id) {
document.getElementById(id).style.display=""
}
This process is still quite slow, as the user is allowed to select multiple attributes (say X,Y).
Is there a faster way of hiding the rows?
Would it be faster if I could somehow detach the table from the DOM, make the changes, and then re-attach? How do I do this in javascript?
Are there other more efficient/smarter ways of doing the filtering?
Thanks :)
I would ask
Why you want to write this code for yourself? From personal experience, trying to filter efficiently and on all browsers is a non-trivial task.
If you are doing this as a learning experience, then look at source of the packages listed below as examples.
With 5000 rows, it would be more efficient to do server side filtering and sorting. Then use ajax to update the displayed table.
I would suggest that you look at using one of the several JavaScript packages that already do this. There are many more packages that the two below. I'm showing these two as examples of what is available.
http://datatables.net/ - This is a very full featured package that handles both client and server side filtering and sorting.
http://www.listjs.com/ - is a lightweight client side filtering and sorting package.
Using AngularJS can indeed be a good idea,
which lets us render your rows as simple as
<tr ng-repeat="row in rowArray">
<td>{{row.id}}</td>
<td>{{row.attr}}</td>
</tr>
where you only need to supply your rowArray as array of objects like {id: 1, attr: 'X'}, see the documentation for ng-repeat directive. One of Angular's big powers lies in its extremely compact code.
Among other things, Angular also has powerful filter building library to filter and sort your rows on the fly right inside your HTML:
<tr ng-repeat="row in rowArray | yourCustomFilter:parameters">
<td>{{row.id}}</td>
<td>{{row.attr}}</td>
</tr>
Having said that, it'll clearly be a performance drag to throw 5K rows into your array. That would create a huge HTML in your browser memory that, however, will not fit into your viewport. Then there is no point to have it in the memory if you can't show it anyway. Instead you only want to have the viewable part in your memory plus possibly a few more rows around.
Have a look at the directive "Scroll till you drop" provided by
Angular UI Utils - it does exactly that!
Pagination as mentioned in another answer is surely a valid alternative to the infinite scroll. There is lot written on the web about strengths and weaknesses of pagination vs infinite scroll if you want to dig into that.
Speaking of your code specifically, it has other performance drags.
For instance, on each call, this function
document.getElementById(id).style.display="none"
will look up the DOM for the element by its id, and then will look up its property .style (which can be a drag if the JavaScript needs to go high up in the Prototype chain). You could do much better performance wise by caching direct reference links to the display properties, which are the ones you really need.
EDIT.
By caching here I mean pre-compiling a hash linking id with the interesting properties:
hash[id] = document.getElementById(id).style.display
Then you switch the style by simple setting:
hash[id] = 'none'
hash[id] = 'block'
This way of calculating hash assumes that your elements are all inside the DOM, which is bad for performance, but there are better ways!
Libraries like jQuery and, of course, Angular :) will let you create your HTML elements with their complete style properties but without attaching them to the DOM. That way you are not overloading your browser's capacity. But you can still cache them! So you will cache your HTML (but not DOM) Elements and their Display like that:
elem[id] = $('<tr>' +
'<td>' + id + '</td>' +
'<td>' + attr + '</td>' +
</tr>');
display[id] = elem[id].style.display;
and then attach/ detach your elements to the DOM as you go and update their display properties using the display cache.
Finally note that for better performance, you want to concatenate your rows in a bundle first, and only then attach in a single jump (instead of attaching one-by-one). The reason is, every time your change the DOM, the browser has to do a lot of recalculation to adjust all other DOM elements correctly. There is a lot going on there, so you want to minimize those re-calculations as much as possible.
POST EDIT.
To illustrate by an example, if parentElement is already in your DOM, and you want to attach an array of new elements
elementArray = [rowElement1, ..., rowElementN]
the way you want to do it is:
var htmlToAppend = elementArray.join('');
parentElement.append(htmlToAppend);
as opposed to running a loop attaching one rowElement at a time.
Another good practice is to hide your parentElement before attaching, then only show when everything is ready.
Your best option is to not render all those things and store object versions of them and only show a max of 50 rows at a time via pagination. Storing that many objects in memory, in JS is no problem. Storing all of those in DOM on the other hand will bring browsers to their knees. 5000 is at around the upper bound of what a browser can do on a good machine while maintaining decent performance. If you start modifying some of those rows and tweaking things ('hiding', 'showing') things definitely will get even slower.
The steps would look something like:
Organize the data into an array of objects, your hash map is great for supplementary and quick access purposes.
Write some sorting and filtering functions that will give you the subsets of data you need.
Write a paginator so you can grab sets of data and then get the next set based on some modified params
Replace your "draw/render" or "update" method with something that displays the current set of 50 that meets the criteria entered.
The following code should be considered pseudo code that probably works:
// Represents each row in our table
function MyModelKlass(attributes) {
this.attributes = attributes;
}
// Represents our table
function CollectionKlass() {
this.children = [];
this.visibleChildren = [];
this.limit = 50;
}
CollectionKlass.prototype = {
// accepts a callback to determine if things are in or out
filter: function(callback) {
// filter doesn't work in every browser
// you can loop manually or user underscorejs
var filteredObjects = this.children.filter(callback);
this.visibleChildren = filteredObjects;
this.filteredChildren = filteredObjects;
this.showPage(0);
},
showPage: function(pageNumber) {
// TODO: account for index out of bounds
this.visibleChildren = this.filteredChildren.slice(
pageNumber * this.limit,
(pageNumber + 1) * this.limit
);
},
// Another example mechanism, comparator is a function
// sort is standard array sorting in JS
sort: function(comparator) {
this.children.sort(comparator);
}
}
function render(el, collection, templateContent) {
// this part is hard due to XSS
// you need to sanitize all data being written or
// use a templating language. I'll opt for
// handlebars style templating for this example.
//
// If you opt for no template then you need to do a few things.
// Write then read all your text to a detached DOM element to sanitize
// Create a detached table element and append new elements to it
// with the sanitized data. Once you're done assembling attach the
// element into the DOM. By attach I mean 'appendChild'.
// That turns out to be mostly safe but pretty slow.
//
// I'll leave the decisions up to you.
var template = Handlebars.compile(templateContent);
el.innerHTML(template(collection));
}
// Lets init now, create a collection and some rows
var myCollection = new CollectionKlass();
myCollection.children.push(new MyModelKlass({ 'a': 1 }));
myCollection.children.push(new MyModelKlass({ 'a': 2 }));
// filter on something...
myCollection.filter(function(child) {
if (child.attributes.a === 1) {
return false;
}
return true;
});
// this will throw an out of bounds error right now
// myCollection.showPage(2);
// render myCollection in some element for some template
render(
document.getElementById('some-container-for-the-table'),
myCollection,
document.getElementById('my-template').innerHTML()
);
// In the HTML:
<script type="text/x-handlebars-template" id="my-template">
<ul>
{{#each visibleChildren}}
<li>{{a}}</li>
{{/each}}
</ul>
</script>
I whipped up a filtering solution that you might want to check out.
Features
can process a 5000 row table almost instantly*
uses plain old JavaScript; no need for libraries
no new syntax to learn; using it is as easy as calling a function
works fine with your preexisting table; no need to start from scratch
no data structures or caching required
supports multiple values per filter and multiple filters
supports inclusive and exclusive filtering
works just as well on a table that's detached from the DOM if you want to apply filters before displaying it.
How it works
The JavaScript is very simple. All it does is create a unique class name for each filter and add it to every row that matches the filter parameters. The class names can be used to determine which rows a given filter is currently filtering, so there's no need to store that information in a data structure. The classes share a common prefix, so they can all be targeted by the same CSS selector for applying the display: none declaration. Removing a filter is as simple as removing its associated class name from the rows that have it.
The Code
If you want to show only rows that have a value of "X" or "Y" in column 2, the function call would look something like this:
addFilter(yourTable, 2, ['X','Y']);
That's all there is to it! Instructions on removing a filter can be found in the demo code below.
Demo
The demo in the code snippet below allows you to apply any number of filters with any number of values to a 5000 row table like the one the OP described, and remove them afterward. It may look like a lot of code, but most of it is just for setting up the demo interface. If you were to use this solution in your own code, you'd probably just copy over the first two js functions (addFilter and removeFilter), and the first CSS rule (the one with display: none).
/*
The addFilter function is ready to use and should work with any table. You just need
to pass it the following arguments:
1) a reference to the table
2) the numeric index of the column to search
3) an array of values to search for
Optionally, you can pass it a boolean value as the 4th argument; if true, the filter
will hide rows that DO contain the specified values rather than those that don't (it
does the latter by default). The return value is an integer that serves as a unique
identifier for the filter. You'll need to save this value if you want to remove the
filter later.
*/
function addFilter(table, column, values, exclusive) {
if(!table.hasAttribute('data-filtercount')) {
table.setAttribute('data-filtercount', 1);
table.setAttribute('data-filterid', 0);
var filterId = 0;
}
else {
var
filterCount = parseInt(table.getAttribute('data-filtercount')) + 1,
filterId = filterCount === 1 ?
0 : parseInt(table.getAttribute('data-filterid')) + 1;
table.setAttribute('data-filtercount', filterCount);
table.setAttribute('data-filterid', filterId);
}
exclusive = !!exclusive;
var
filterClass = 'filt_' + filterId,
tableParent = table.parentNode,
tableSibling = table.nextSibling,
rows = table.rows,
rowCount = rows.length,
r = table.tBodies[0].rows[0].rowIndex;
if(tableParent)
tableParent.removeChild(table);
for(; r < rowCount; r++) {
if((values.indexOf(rows[r].cells[column].textContent.trim()) !== -1) === exclusive)
rows[r].classList.add(filterClass);
}
if(tableParent)
tableParent.insertBefore(table, tableSibling);
return filterId;
}
/*
The removeFilter function takes two arguments:
1) a reference to the table that has the filter you want to remove
2) the filter's ID number (i.e. the value that the addFilter function returned)
*/
function removeFilter(table, filterId) {
var
filterClass = 'filt_' + filterId,
tableParent = table.parentNode,
tableSibling = table.nextSibling,
lastId = table.getAttribute('data-filterid'),
rows = table.querySelectorAll('.' + filterClass),
r = rows.length;
if(tableParent)
tableParent.removeChild(table);
for(; r--; rows[r].classList.remove(filterClass));
table.setAttribute(
'data-filtercount',
parseInt(table.getAttribute('data-filtercount')) - 1
);
if(filterId == lastId)
table.setAttribute('data-filterid', parseInt(filterId) - 1);
if(tableParent)
tableParent.insertBefore(table, tableSibling);
}
/*
THE REMAINING JS CODE JUST SETS UP THE DEMO AND IS NOT PART OF THE SOLUTION, though it
does provide a simple example of how to connect the above functions to an interface.
*/
/* Initialize interface. */
(function() {
var
table = document.getElementById('hugeTable'),
addFilt = function() {
var
exclusive = document.getElementById('filterType').value === '0' ? true : false,
colSelect = document.getElementById('filterColumn'),
valInputs = document.getElementsByName('filterValue'),
filters = document.getElementById('filters'),
column = colSelect.value,
values = [],
i = valInputs.length;
for(; i--;) {
if(valInputs[i].value.length) {
values[i] = valInputs[i].value;
valInputs[i].value = '';
}
}
filters.children[0].insertAdjacentHTML(
'afterend',
'<div><input type="button" value="Remove">'
+ colSelect.options[colSelect.selectedIndex].textContent.trim()
+ (exclusive ? '; [' : '; everything but [') + values.toString() + ']</div>'
);
var
filter = filters.children[1],
filterId = addFilter(table, column, values, exclusive);
filter.children[0].addEventListener('click', function() {
filter.parentNode.removeChild(filter);
removeFilter(table, filterId);
});
},
addFiltVal = function() {
var input = document.querySelector('[name="filterValue"]');
input.insertAdjacentHTML(
'beforebegin',
'<input name="filterValue" type="text" placeholder="value">'
);
input.previousElementSibling.focus();
},
remFiltVal = function() {
var input = document.querySelector('[name="filterValue"]');
if(input.nextElementSibling.name === 'filterValue')
input.parentNode.removeChild(input);
};
document.getElementById('addFilterValue').addEventListener('click', addFiltVal);
document.getElementById('removeFilterValue').addEventListener('click', remFiltVal);
document.getElementById('addFilter').addEventListener('click', addFilt);
})();
/* Fill test table with 5000 rows of random data. */
(function() {
var
tbl = document.getElementById('hugeTable'),
num = 5000,
dat = [
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z'
],
len = dat.length,
flr = Math.floor,
rnd = Math.random,
bod = tbl.tBodies[0],
sib = bod.nextSibling,
r = 0;
tbl.removeChild(bod);
for(; r < num; r++) {
bod.insertAdjacentHTML(
'beforeend',
'<tr><td>' + r + '</td><td>' + dat[flr(rnd() * len)] + '</td></tr>');
}
tbl.insertBefore(bod, sib);
})();
[class*="filt_"] {display: none;} /* THIS RULE IS REQUIRED FOR THE FILTERS TO WORK!!! */
/* THE REMAINING CSS IS JUST FOR THE DEMO INTERFACE AND IS NOT PART OF THE SOLUTION. */
h3 {margin: 0 0 .25em 0;}
[name="filterValue"] {width: 2.5em;}
[class*="filt_"] {display: none;}
#addFilter {margin-top: .5em;}
#filters {margin-left: .5em;}
#filters > div {margin-bottom: .5em;}
#filters > div > input, select {margin-right: .5em;}
#filters, #hugeTable {
float: left;
border: 1px solid black;
padding: 0 .5em 0 .5em;
white-space: nowrap;
}
#hugeTable {border-spacing: 0;}
#hugeTable > thead > tr > th {
padding-top: 0;
text-align: left;
}
#hugeTable > colgroup > col:first-child {min-width: 4em;}
<h3>Add Filter</h3>
Column:
<select id="filterColumn">
<option value="1">attr</option>
<option value="0">id</option>
</select>
Action:
<select id="filterType">
<option value="0">filter out</option>
<option value="1">filter out everything but</option>
</select>
Value(s):
<input id="addFilterValue" type="button" value="+"
><input id="removeFilterValue" type="button" value="-"
><input name="filterValue" type="text" placeholder="value">
<br>
<input id="addFilter" type="button" value="Apply">
<hr>
<table id="hugeTable">
<col><col>
<thead>
<tr><th colspan="2"><h3>Huge Table</h3></th></tr>
<tr><th>id</th><th>attr</th></tr>
</thead>
<tbody>
</tbody>
</table>
<div id="filters">
<h3>Filters</h3>
</div>
*Performance will vary depending on how much CSS is being applied to the table rows and cells, and whether that CSS was written with performance in mind. Whatever filtering strategy you use, there's not much you can do to make a heavily- or inefficiently-styled table perform well, other than load less of it (as others have suggested).
see this link it might help, the only problem is its not in pure javascript it also uses angularjs.
app.service("NameService", function($http, $filter){
function filterData(data, filter){
return $filter('filter')(data, filter)
}
function orderData(data, params){
return params.sorting() ? $filter('orderBy')(data, params.orderBy()) : filteredData;
}
function sliceData(data, params){
return data.slice((params.page() - 1) * params.count(), params.page() * params.count())
}
function transformData(data,filter,params){
return sliceData( orderData( filterData(data,filter), params ), params);
}
var service = {
cachedData:[],
getData:function($defer, params, filter){
if(service.cachedData.length>0){
console.log("using cached data")
var filteredData = filterData(service.cachedData,filter);
var transformedData = sliceData(orderData(filteredData,params),params);
params.total(filteredData.length)
$defer.resolve(transformedData);
}
else{
console.log("fetching data")
$http.get("data.json").success(function(resp)
{
angular.copy(resp,service.cachedData)
params.total(resp.length)
var filteredData = $filter('filter')(resp, filter);
var transformedData = transformData(resp,filter,params)
$defer.resolve(transformedData);
});
}
}
};
return service;
});
Here is a on the fly filter solution, that filter the table using letters typed in input box on keypress event.
Though right now I am using DataTables in my current project development, yet if you want a strict javascript solution here is it. It may not be the best optimized but works good.
function SearchRecordsInTable(searchBoxId, tableId) {
var searchText = document.getElementById(searchBoxId).value;
searchText = searchText.toLowerCase();
var targetTable = document.getElementById(tableId);
var targetTableColCount;
//Loop through table rows
for (var rowIndex = 0; rowIndex < targetTable.rows.length; rowIndex++) {
var rowData = '';
//Get column count from header row
if (rowIndex == 0) {
targetTableColCount = targetTable.rows.item(rowIndex).cells.length;
continue; //do not execute further code for header row.
}
//Process data rows. (rowIndex >= 1)
for (var colIndex = 0; colIndex < targetTableColCount; colIndex++) {
rowData += targetTable.rows.item(rowIndex).cells.item(colIndex).textContent;
rowData = rowData.toLowerCase();
}
console.log(rowData);
//If search term is not found in row data
//then hide the row, else show
if (rowData.indexOf(searchText) == -1)
targetTable.rows.item(rowIndex).style.display = 'none';
else
targetTable.rows.item(rowIndex).style.display = '';
}
}
Cheers!!
More than searching, rendering eats up a lot of time and resources. Limit the number of rows to display and your code can work like a charm. Also instead of hiding and unhiding, if you print only limited rows, that would be better. You can check out how it's done in my opensource library https://github.com/thehitechpanky/js-bootstrap-tables
function _addTableDataRows(paramObjectTDR) {
let { filterNode, limitNode, bodyNode, countNode, paramObject } = paramObjectTDR;
let { dataRows, functionArray } = paramObject;
_clearNode(bodyNode);
if (typeof dataRows === `string`) {
bodyNode.insertAdjacentHTML(`beforeend`, dataRows);
} else {
let filterTerm;
if (filterNode) {
filterTerm = filterNode.value.toLowerCase();
}
let serialNumber = 0;
let limitNumber = 0;
let rowNode;
dataRows.forEach(currentRow => {
if (!filterNode || _filterData(filterTerm, currentRow)) {
serialNumber++;
if (!limitNode || limitNode.value === `all` || limitNode.value >= serialNumber) {
limitNumber++;
rowNode = _getNode(`tr`);
bodyNode.appendChild(rowNode);
_addData(rowNode, serialNumber, currentRow, `td`);
}
}
});
_clearNode(countNode);
countNode.insertAdjacentText(`beforeend`, `Showing 1 to ${limitNumber} of ${serialNumber} entries`);
}
if (functionArray) {
functionArray.forEach(currentObject => {
let { className, eventName, functionName } = currentObject;
_attachFunctionToClassNodes(className, eventName, functionName);
});
}
}

How to vertically align a list on one item, with HTML/CSS/JavaScript?

I have an HTML document with a very long list, like this:
<ol>
<li>frog</li>
<li>fish</li>
<li>flamingo</li>
<li>ferret</li>
<li><div class="marked">fox</div></li>
...
</ol>
The list is too long for all items to be visible on the screen. I've marked one item with div class="marked"> and </div>. Is there some way to vertically center the list on that marked item.
If the list is too long, it should run off the edge of the visible screen, with no scrollbars created.
Here is an example of how it appears in the browser window:
__________________
| 2.fish |
| 3.flamingo |
| 4.ferret | <-- This marked item is centered.
| 5.elephant |
|___6.mouse________| <-- No scrollbars.
How can I vertically align a list on a marketed item?
Here's a solution based on James G.'s code, which avoids the loop by using getBoundingClientRect():
http://jsfiddle.net/d8d5jjvc/4/
var list = document.getElementById('list'),
marked= document.getElementById('marked'),
crm= marked.getBoundingClientRect(),
height= crm.bottom-crm.top;
list.scrollTop= (crm.top-height)-(list.clientHeight-height)/2;
Ok, I really hope I understood you correctly, because this took WAY longer than I expected.
Working JSfiddle
First you have to find the element you're centering on, the list, the height of the items in the list, and the index of the element you're centering on. (I stole a function from here to do that last bit, as well as changing your class to an id. You can figure that out if it needs to be a class)
function arrayObjectIndexOf(myArray, searchTerm) {
for(var i = 0, len = myArray.length; i < len; i++) {
if (myArray[i].firstChild === searchTerm) return i;
}
return -1;
}
var list = document.getElementById('list'),
listItems = list.children,
listItemHeight = list.scrollHeight / listItems.length,
target = elem,
index = arrayObjectIndexOf(listItems, target);
Then the tricky part was the math. First we get the height of elements above the element we're centering on, then we subtract the height of the container, minus the item we're centering on, over 2. If that makes any sense. You might want to draw it if you don't understand the math, it'll make more sense on paper.
list.scrollTop = (index * listItemHeight) - ((list.clientHeight - listItemHeight) / 2);
You display a list vertically with the display option.
display: inline;
I usually use the inline-block parameter, this is
display: inline-block;
Then, you've to do this on your css.
.marked: { display: inline-block; }
I hope this works for you.

jQuery UI Sortable containment unable to detect placeholder

So I was working on this application where I want people to be able to drag items from one table data to the other table data, which must be contained within the parent table row.
But whenever I drag it around it seems to stick to the containment excluding the height of any placeholders.
Try it yourself: http://jsfiddle.net/2wy8R/
Any idea of how I can make it select the parent of the parent? Of not, then, how can I make the placeholders count?
Greetings
.
Update: YouTube video of the problem http://youtu.be/PMXcQvJmRGw
OK, here you go. Overridden the default containment as it seems buggy with your scenario. Let me know if this is not a good idea but it seems to work:
http://jsfiddle.net/2wy8R/6/
$('#first, #second').sortable({
connectWith: '.sortable',
placeholder: 'placeholder',
start: function(event, ui) {
ui.placeholder.height(ui.item.height() - 4);
var p = $(ui.helper);
var tr = p.closest("tr");
$(document).mousemove(function(e) {
var pOffset = p.offset();
var trOffset = tr.offset();
if (pOffset.left < trOffset.left) {
p.css({left: trOffset.left});
}
if (pOffset.left + p.width() > trOffset.left + tr.width()) {
p.css({left: trOffset.left + tr.width() - p.width()});
}
if (pOffset.top < trOffset.top) {
p.css({top: trOffset.top});
}
if (pOffset.top + p.height() > trOffset.top + tr.height()) {
p.css({top: trOffset.top + tr.height() - p.height()});
}
});
}
}).disableSelection();
just be careful with it though, as this keeps adding mousemove events to the document. you may want to unbind mousemove before binding it...

Draw arrow between lists

Is there any way to dynamically draw an arrow between the two highlighted list items?
So if I hovered over "Item 2" it would do this (but a straight arrow):
Item 1 Highlight 3
Item 2-----\ Highlight 1
Item 3 ----->Highlight 2
This is the code from the answer I got here a few mins ago:
Highlight item in two lists when mouseover
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
$("#qq" + n + ", #aa" + n).toggleClass("highlight");
});
jsfiddle: http://jsfiddle.net/e37Yg/1/
<ul class="list1">
<li id="qq1">sdfsdv</li>
<li id="qq2">bnvnvb</li>
<li id="qq3">nmnutymnj7</li>
<li id="qq4">cvbc</li>
<li id="qq5">45tsgd</li>
</ul>
<ul class="list2">
<li id="aa3">fgtbrtgb</li>
<li id="aa1">vbn xgbn</li>
<li id="aa5">vdgver</li>
<li id="aa4">asdasdv</li>
<li id="aa2">nvfbnfn</li>
</ul>
You don't have to use 2D drawing here. Check this out: http://jsfiddle.net/vjYuW/
I just forked and updated the fiddle you have posted above.
Here is the essential code, it handles 3 DIVs 1 pixel wide or tall to draw the lines:
HTML:
...left list...
<div id="segment1" class="hline"></div>
<div id="segment2" class="vline"></div>
<div id="segment3" class="hline"></div>
...right list...
CSS:
... formatting for ULs here, both have to be float:left...
.highlight { background-color: red; }
.hline {
display:block;
position:relative;
float:left;
height: 1px;
width: 7em;
}
.vline {
display:block;
position:relative;
float:left;
height: 1px;
width: 1px;
}
JavaScript:
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
var leftY = $('#qq' + n).position().top;
var rightY = $('#aa' + n).position().top;
var H = Math.abs(rightY-leftY);
if (H == 0) H = 1;
$('#segment1').css('top',leftY+'px');
$('#segment3').css('top',rightY+'px');
$('#segment2').css('top',Math.min(leftY,rightY)+'px');
$('#segment2').css('height',H+'px');
$("#qq" + n + ", #aa" + n + ",#segment1,#segment2,#segment3").toggleClass("highlight");
});
Note: you will probably have to tweak it a little to support all browsers - I didn't check IE6 & Co.
You can use the HTML5 canvas element to achieve this.
I'm not sure if this is the best way to do it, but I fiddled around and got this.
What I did is first I enclosed the lists in a div. The div is styled with CSS to have a relative position. This is so when you get the position with jQuery, it will give a position relative to that. Next, I put a canvas in front of the lists and disabled pointer events on it. I also added something to adjust the height of the canvas to the height of the lists. Then I added another handler for hover. When you hover over it, it will draw the arrow, and when you hover out, it'll clear the canvas.
To draw the arrow is fairly simple. First it gets the positions of the items. Then it draws a line and uses some math to orient the arrow. To get the positions is fairly easy. For the right list, you can just use the position method. For the left list, I created a temporary span and appended it to the list item, and then got the position of that.
I think for something like this you may want to use a third party drawing library such as Vector Draw Library.
You can download the library from the link and add it to your app. Then:
Include it on your page:
<script type="text/javascript" src="wz_jsgraphics.js"></script>
Then add to your hover function:
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
$("#qq" + n + ", #aa" + n).toggleClass("highlight");
//canvas is your drawing div
var jg = new jsGraphics("canvas");
jg.drawLine($("#qq" + n).offset().left + 30, $("#qq" + n).offset().top , $("#aa" + n).offset().left, $("#aa" + n).offset().top );
jg.paint();
Note that you will have to write the code to remove the line in the hover function otherwise once it is drawn it will remain. Also, I am using offset() to calculate the position of the items in the list. This should work but you may have to tweak a bit to get it to look right.
The above code works but is not complete. Maybe the second function in the hover can call clear() on the canvas. Canvas here is the enclosing div that encloses the two lists.
<script src='www.walterzorn.de/en/scripts/wz_jsgraphics.js'> </script>
function drawLine(element1, element2) {
var jg = new jsGraphics("renderGraph");
var ele1 = document.getElementById(element1);
var ele2 = document.getElementById(element2);
jg.setColor("#DDD");
jg.setStroke(5);
jg.drawLine(ele1.offsetLeft + ele1.offsetWidth/2 , ele1.offsetTop + ele1.offsetHeight/2, ele2.offsetLeft + ele2.offsetWidth/2, ele2.offsetTop + ele2.offsetHeight/2);
jg.paint();
}

row striping and first/last classes with mustache.js

Frequently one wants to treat the first and/or last items in a list differently from the others. is there a way to do that using mustache? what about row striping?
(Obviously, one could always use jquery or whatever to apply a css class after the template has been processed, or whatever, but I'm wondering about something more at the template level.)
Mustache is very light, so AFAIK, it does not provide that feature.
You can use something like that, to get even/odd class:
var view = {
arr: ['one', 'two', 'three'],
clazz: function() {
return _counter++ % 2 == 0 ? 'even' : 'odd';
}
};
var template = '{{#arr}}<span class="{{clazz}}">{{.}}</span>{{/arr}}';
Mustache.to_html(template, view);
Or preprocess the data first, something like that:
function preprocessArrayWithFirstLastClass(src) {
var clazz;
for (var i = 0; i < src.length; i++) {
clazz = i % 2 == 0 ? 'even' : 'odd';
if (i == 0) clazz += ' first';
if (i == src.length - 1) clazz += ' last';
src[i].clazz = clazz;
}
}
var view = {
arr: preprocessArrayWithFirstLastClass([{name: 'one'}, {name: 'two'}, {name: 'three'}])
};
var template = '{{#arr}}<span class="{{clazz}}">{{name}}</span>{{/arr}}';
Mustache.to_html(template, view);
I recommend doing both of these with pure css/css3, no js required! This seems ideal when the stuff you're trying to do is not dealing with content. The future is now!:
Css row striping:
use nth-child();
http://dev.opera.com/articles/view/zebra-striping-tables-with-css3/
This won't display for ie7 and ie8 ( http://caniuse.com/#search=nth-child ), but they still get the content, so I consider it a win.
Styling the last element of a static list:
#nav li + li + li{
// Crazy styles on the 3rd li here!
}
(has good support: http://caniuse.com/#search=sibling )
Styling the last element of a dynamic list
Use :last-child.
div#test p:last-child {color: red;}
div#test p:first-child {text-decoration: underline;}
:last-child isn't supported in ie7 and ie8 ( http://caniuse.com/#search=last-child ), so be careful that you're doing something that would degrade gracefully here. Strangely, :first-child is, so it's possible you can, say, put coloring on all elements by default and then explicitly remove them from the first child, and that will actually work in all browsers.

Categories