How to do a .less mixin that generates something of a grid in css? - background-image

I want to generate a matrix of css selector in the form of .cX.rY selectors for a grid of something like 10 x 10. But I'm not seeing how I can do this less (I'm pretty new to .less). I'm using DotLess too, so perhaps there are some built-in limitations with that; I don't know for sure on that front.
#col-width: -24px;
#row-width: -24px;
.img-pos(#col, #row) {
background-position:
(((#col - 1) * #col-width) - 1)
(((#row - 1) * #row-width) - 1);
}
.c2.r1 { .img-pos(2, 1); }
.c2.r2 { .img-pos(2, 2); }
.c2.r3 { .img-pos(2, 3); }
.c2.r4 { .img-pos(2, 4); }
.c2.r5 { .img-pos(2, 5); }
.c2.r6 { .img-pos(2, 6); }
.c2.r7 { .img-pos(2, 7); }
...
...
.cX.rY { .img-pos(2, 7); }
Is this possible? If so, how?

Here is some code that allows you set the max columns and rows, as well as optionally set the start column and row number (default: 1), and optionally set the class indicator for them (defaults to "c" and "r"). It uses a looping technique in LESS to auto generate the code.
LESS Code
#col-width: -24px;
#row-width: -24px;
.img-pos(#col, #row) {
background-position:
(((#col - 1) * #col-width) - 1)
(((#row - 1) * #row-width) - 1);
}
.generateGridSelectors(#maxCols, #maxRows, #colStart: 1, #rowStart: 1, #colSel: "c", #rowSel: "r") when (#maxCols > 0) and (#colStart < #maxCols) and (#maxRows > 0) and (#rowStart < #maxRows) {
#colStop: #maxCols + 1;
#rowStop: #maxRows + 1;
.makeGrid(#c: #colStart) when (#c < (#maxCols + 1)) {
.setRow(#r: #rowStart) when (#r < (#maxRows + 1)) {
//generate selector and position
(~".#{colSel}#{c}.#{rowSel}#{r}") {
.img-pos(#c, #r);
}
//interate next row
.setRow(#r + 1);
}
//end row loop
.setRow(#rowStop) {}
//start row loop
.setRow();
//iterate next column
.makeGrid(#c + 1);
}
//end column loop
.makeGrid(#colStop) {}
//start grid loops
.makeGrid();
} //end generateGridSelectors
//call your build (not sure how well it will handle real large numbers)
.generateGridSelectors(10, 10);
CSS Output
.c1.r1 {
background-position: -1px -1px;
}
.c1.r2 {
background-position: -1px -25px;
}
.c1.r3 {
background-position: -1px -49px;
}
...
...
.c10.r8 {
background-position: -217px -169px;
}
.c10.r9 {
background-position: -217px -193px;
}
.c10.r10 {
background-position: -217px -217px;
}

Related

ExtJS - Attributes changes are being ignored during drag action

We are using ExtJS 4.2.1. The width/height attributes of the img won't change in below example when element is dragged beyond the +/-5px y-coordinate. Changes to attribute 'show' are also ignored. However, element can be destroyed and re-created, but this is not desired.
[panel]
var dndLinkSprite = me.surface.add({
type: 'image',
x: bBox.x,
y: bBox.y,
width: 16,
height: 16,
src: '/link.png'
})
...
dragAction: function(panel, e, diff, dndConfig) {
var spriteLink = panel.dndLinkSprite;
if ( diff[1] > 5 || diff[1] < -5 ) {
spriteLink.setAttributes(height, 16);
spriteLink.setAttributes(width, 16);
} else {
spriteLink.setAttributes(height, 0);
spriteLink.setAttributes(width, 0);
};
}
Thanks for your help!
Solved - wrong syntax:
Instead of:
spriteLink.setAttributes(height, 0);
spriteLink.setAttributes(width, 0);
use:
spriteLink.setAttributes({width: 0, height:0} , true);
spriteLink.getEl().dom.setAttribute(height, 16);

Chart.js and long labels

I use Chart.js to display a Radar Chart. My problem is that some labels are very long :
the chart can't be display or it appears very small.
So, is there a way to break lines or to assign a max-width to the labels?
Thank you for your help!
For Chart.js 2.0+ you can use an array as label:
Quoting the DOCs:
"Usage: If a label is an array as opposed to a string i.e. [["June","2015"], "July"] then each element is treated as a seperate line."
var data = {
labels: [["My", "long", "long", "long", "label"], "another label",...],
...
}
With ChartJS 2.1.6 and using #ArivanBastos answer
Just pass your long label to the following function, it will return your label in an array form, each element respecting your assigned maxWidth.
/**
* Takes a string phrase and breaks it into separate phrases
* no bigger than 'maxwidth', breaks are made at complete words.
*/
function formatLabel(str, maxwidth){
var sections = [];
var words = str.split(" ");
var temp = "";
words.forEach(function(item, index){
if(temp.length > 0)
{
var concat = temp + ' ' + item;
if(concat.length > maxwidth){
sections.push(temp);
temp = "";
}
else{
if(index == (words.length-1)) {
sections.push(concat);
return;
}
else {
temp = concat;
return;
}
}
}
if(index == (words.length-1)) {
sections.push(item);
return;
}
if(item.length < maxwidth) {
temp = item;
}
else {
sections.push(item);
}
});
return sections;
}
console.log(formatLabel("This string is a bit on the longer side, and contains the long word Supercalifragilisticexpialidocious for good measure.", 10))
To wrap the xAxes label, put the following code into optoins. (this will split from white space and wrap into multiple lines)
scales: {
xAxes: [
{
ticks: {
callback: function(label) {
if (/\s/.test(label)) {
return label.split(" ");
}else{
return label;
}
}
}
}
]
}
You can write a javascript function to customize the label:
// Interpolated JS string - can access value
scaleLabel: "<%=value%>",
See http://www.chartjs.org/docs/#getting-started-global-chart-configuration
Unfortunately there is no solution for this until now (April 5th 2016).
There are multiple issues on Chart.js to deal with this:
https://github.com/nnnick/Chart.js/issues/358 (closed with fix)
https://github.com/nnnick/Chart.js/issues/608 (closed with no fix)
https://github.com/nnnick/Chart.js/issues/358 (closed with no fix)
https://github.com/nnnick/Chart.js/issues/780 (closed with no fix)
https://github.com/nnnick/Chart.js/issues/752 (closed with no fix)
This is a workaround: Remove x-axis label/text in chart.js
It seems you might be actually be talking about data labels and not the scale labels. In this case you'd want to use the pointLabelFontSize option. See below example:
var ctx = $("#myChart").get(0).getContext("2d");
var data = {
labels: ["Eating", "Sleeping", "Coding"],
datasets: [
{
label: "First",
strokeColor: "#f00",
pointColor: "#f00",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "#ccc",
data: [45, 59, 90]
},
{
label: "Second",
strokeColor: "#00f",
pointColor: "#00f",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "#ccc",
data: [68, 48, 40]
}
]
};
// This is the important part
var options = {
pointLabelFontSize : 20
};
var myRadarChart = new Chart(ctx).Radar(data, options);
Finally you may want to play with the dimensions of your < canvas > element as I've found sometimes giving the Radar chart more height helps the auto scaling of everything.
I found the best way to manipulate the labels on the radar chart was by using the pointlabels configuration from Chartjs.
let skillChartOptions = {
scale: {
pointLabels: {
callback: (label: any) => {
return label.length > 5 ? label.substr(0, 5) + '...' : label;
},
}, ...
}, ...
}
I'd like to further extend on Fermin's answer with a slightly more readable version. As previously pointed out, it's possible to give Chart.js an array of strings to make it wrap the text. To make this array of strings from a longer string, I propose this function:
function chunkString(str, maxWidth){
const sections = [];
const words = str.split(" ");
let builder = "";
for (const word of words) {
if(word.length > maxWidth) {
sections.push(builder.trim())
builder = ""
sections.push(word.trim())
continue
}
let temp = `${builder} ${word}`
if(temp.length > maxWidth) {
sections.push(builder.trim())
builder = word
continue
}
builder = temp
}
sections.push(builder.trim())
return sections;
}
const str = "This string is a bit on the longer side, and contains the long word Supercalifragilisticexpialidocious for good measure."
console.log(str)
console.log(chunkString(str, 10))
.as-console-wrapper {
max-height: 100vh!important;
}
For most of the recent versions of chart.js, the labels can be mentioned as array of arrays. That's your labels can be:
labels = [['a', 'label1'],['the', 'lable2'],label3] '$'
You can use following function which is fast and compatible across all versions for converting your labels array into array of array in case the labels contain multiple words:
function splitLongLabels(labels){
//labels = ["ABC PQR", "XYZ"];
var i = 0, len = labels.length;
var splitlabels = labels;
while (i < len) {
var words = labels[i].trim().split(' ');
if(words.length>1){
for(var j=0; j<words.length; j++){
}
splitlabels[i] = words;
}
i++
}
return splitlabels;
}

Dynamic class names in LESS

I have the following bit of LESS code working
#iterations: 940;
#iterations: 940;
#col:2.0833333333333333333333333333333%;
// helper class, will never show up in resulting css
// will be called as long the index is above 0
.loopingClass (#index) when (#index > -20) {
// create the actual css selector, example will result in
// .myclass_30, .myclass_28, .... , .myclass_1
(~".gs#{index}") {
// your resulting css
width: (#index/20+1)*#col;
}
// next iteration
.loopingClass(#index - 60);
}
// end the loop when index is 0
.loopingClass (-20) {}
// "call" the loopingClass the first time with highest value
.loopingClass (#iterations);
It outputs our grid system as so:
.gs940 {
width: 100%;
}
.gs880 {
width: 93.75%;
}
.gs820 {
width: 87.5%;
}
.gs760 {
width: 81.25%;
}
.gs700 {
width: 75%;
}
etc etc etc
Now what I want to do is some math to the class names to produce the following classes
.gs220-700
.gs280-640
.gs340-580
.gs400-520
.gs460-460
.gs520-400
.gs580-340
.gs640-280
.gs700-220
etc etc etc
basically this would be
.(#index) - (920px minus #index)
But I have no idea if this is possible.
This whole question was very helpful to me. I just wanted to post the solution to my problem as the way to do it has changed since LESS v 1.4. LESS Changelog
Rather than using the ~ sign, you just write out the portion of the name that you want along with the normal # and variable name with {} surrounding it. So: #class#{variable}.
For example, my solution using the same sort of loop became such:
/*Total number of passport inserts*/
#numInserts: 5;
/*Total width of the passport foldouts*/
#passportWidth: 300px;
/*Change in passport insert width per iteration*/
#passportWidthDiff: (#passportWidth / #numInserts);
/*"Array" of colors to select from to apply to the id*/
#passportColors:
blue orange green red yellow
purple white teal violet indigo;
/*The faux loop the doesn't end up in the final css
#index is the counter
#numInserts is the total number of loops*/
.loopingClass (#index) when (#index <= #numInserts){
/*This is the created ID with the index appended to it
You can also do this with classes such as if
we had had ".insert#{index}"*/
#insert#{index}{
/*Here are our properties that get adjusted with the index*/
width: (#passportWidth - (#passportWidthDiff * (#numInserts - #index)));
height: 50px;
background-color: extract(#passportColors, #index);
z-index: (#numInserts - #index);
}
/*Here we increment our loop*/
.loopingClass(#index + 1);
}
/*This calls the loop and starts it, I started from 1
since I didn't want to lead a className starting from 0,
But there is no real reason not to. Just remember to
Change your conditional from "<=" to "<"*/
.loopingClass(1);
And produces the following:
#insert1 {
width: 60px;
height: 50px;
background-color: #0000ff;
z-index: 4;
}
#insert2 {
width: 120px;
height: 50px;
background-color: #ffa500;
z-index: 3;
}
#insert3 {
width: 180px;
height: 50px;
background-color: #008000;
z-index: 2;
}
...
I don't think you're far off. What I've done is create a second variable inside the mixin, called #index2. All this does is find the '920px minus #index' value that you're looking for:
#index2 = (920-#index);
this is then appended to the class name:
(~".gs#{index}-#{index2}") {
This is the complete loop:
.loopingClass (#index) when (#index > 160) {
#index2 = (920-#index);
// create the actual css selector, example will result in
// .myclass_30, .myclass_28, .... , .myclass_1
(~".gs#{index}-#{index2}") {
// your resulting css
width: (#index/20+1)*#col;
}
// next iteration
.loopingClass(#index - 60);
}
// "call" the loopingClass the first time with highest value
.loopingClass (#iterations);
In order to get just the set you are looking for (gs220-700 to gs700-220), just change #iterations to equal 700.
Worth noting that currently, this will create the classes in the reverse order of how you specified them in the question.

Why aren't my modifications to image_edit and image_save methods working?

I'm trying to modify redactor's image_edit and image_save methods to add some functionality.
I'm just wrapping te existing img element with a div and add another one with a text. When I press the save button in the modal and look at the source in redactor, it appears OK, but when I save the changes, redactor cuts off both divs and leaves only the original img element and desc value from the form. How can I resolve this?
Original code:
$(el).attr('alt', $('#redactor_file_alt').val());
var floating = $('#redactor_form_image_align').val();
if (floating == 'left') $(el).css({ 'float': 'left', margin: '0 10px 10px 0' });
else if (floating == 'right') $(el).css({ 'float': 'right', margin: '0 0 10px 10px' });
else $(el).css({ 'float': 'none', margin: '0' });
this.modalClose();
My code:
$(el).attr('alt', $('#redactor_file_alt').val());
var floating = $('#redactor_form_image_align').val();
if (floating == 'left') {
var align = 'left-image';
} else if (floating == 'right') {
var align = 'right-image';
} else {
var align = 'ci-image';
}
var imgDiv = $('<div class="' + align + '" />');
$(el).wrap(imgDiv);
if($('#image_desc').val()) {
$(el).after($('<div class="descr" />').html($('#image_desc').val()));
}
You should specify in the settings { removeClasses: false }

Updating the DOM based on scroll position with Twitter Bootstrap?

If you look at this page: http://twitter.github.com/bootstrap/components.html
Note the submenu and it's position. Now scroll down - notice how it changes? I assumed they implemented it with the scrollspy plugin but I can't seem to figure out how to do it, all I can do is update which list element has the active class.
Any help would be much appreciated :)
You can find this bit of code in the application.js file, it doesn't look as though it's included in the download. It's available here...funnily enough the header comment reads
// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
// IT'S ALL JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++
This is the part that does the heavy lifting I believe...haven't tested it, just stepped through the code in Chrome
// fix sub nav on scroll
var $win = $(window)
, $nav = $('.subnav')
, navTop = $('.subnav').length && $('.subnav').offset().top - 40
, isFixed = 0
processScroll()
$win.on('scroll', processScroll)
function processScroll() {
var i, scrollTop = $win.scrollTop()
if (scrollTop >= navTop && !isFixed) {
isFixed = 1
$nav.addClass('subnav-fixed')
} else if (scrollTop <= navTop && isFixed) {
isFixed = 0
$nav.removeClass('subnav-fixed')
}
}
CCBlackburn's answer is close though its missing the required stylesheet. For posterity I've also included the javascript. Anyway I got it working using the following:
Javascript
// fix sub nav on scroll
var $win = $(window)
, $nav = $('.subnav')
, navTop = $('.subnav').length && $('.subnav').offset().top - 40
, isFixed = 0
processScroll()
// hack sad times - holdover until rewrite for 2.1
$nav.on('click', function () {
if (!isFixed) setTimeout(function () { $win.scrollTop($win.scrollTop() - 47) }, 10)
})
$win.on('scroll', processScroll)
function processScroll() {
var i, scrollTop = $win.scrollTop()
if (scrollTop >= navTop && !isFixed) {
isFixed = 1
$nav.addClass('subnav-fixed')
} else if (scrollTop <= navTop && isFixed) {
isFixed = 0
$nav.removeClass('subnav-fixed')
}
}
Stylesheet
/* Fixed subnav on scroll, but only for 980px and up (sorry IE!) */
#media (min-width: 980px) {
.subnav-fixed {
position: fixed;
top: 40px;
left: 0;
right: 0;
z-index: 1020; /* 10 less than .navbar-fixed to prevent any overlap */
border-color: #d5d5d5;
border-width: 0 0 1px; /* drop the border on the fixed edges */
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
-moz-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
}
.subnav-fixed .nav {
width: 938px;
margin: 0 auto;
padding: 0 1px;
}
.subnav .nav > li:first-child > a,
.subnav .nav > li:first-child > a:hover {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
}
/* LARGE DESKTOP SCREENS */
#media (min-width: 1210px) {
/* Update subnav container */
.subnav-fixed .nav {
width: 1168px; /* 2px less to account for left/right borders being removed when in fixed mode */
}
}