Getting Zurb Foundation SASS to Play Nice With Existing Application - sass

I've got an existing NodeJS express / AngularJS application that I need to make play nicely with Zurb foundation and in particular I am having problems with SASS.
The application was initially created with the yeoman angular generator.
I have installed zurb foundation via bower and the css side of things works fine. I can't however get the sass to compile properly. I seem to end up with missing parts of the css so there's virtually no styling present. The stylesheet is injected etc. and there's no errors as such when I build it.
My gruntfile looks like this (consolidated).
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
yeoman: {
client: require('./bower.json').appPath || 'client',
dist: 'dist'
},
express: {
...
},
watch: {
injectJS: {
files: [
'<%= yeoman.client %>/{app,components}/**/*.js',
'!<%= yeoman.client %>/{app,components}/**/*.spec.js',
'!<%= yeoman.client %>/{app,components}/**/*.mock.js',
'!<%= yeoman.client %>/app/app.js'],
tasks: ['injector:scripts']
},
injectCss: {
files: [
'<%= yeoman.client %>/{app,components}/**/*.css'],
tasks: ['injector:css']
},
injectSass: {
files: [
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
tasks: ['injector:sass']
},
sass: {
files: [
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
tasks: ['sass', 'autoprefixer']
},
gruntfile: {
files: ['Gruntfile.js']
},
},
sass: {
server: {
options: {
loadPath: [
'<%= yeoman.client %>/bower_components/foundation/scss',
'<%= yeoman.client %>/bower_components',
'<%= yeoman.client %>/app',
'<%= yeoman.client %>/components'
],
compass: false
},
files: {
'.tmp/app/app.css' : '<%= yeoman.client %>/app/app.scss'
}
}
},
injector: {
scripts: {
...
},
sass: {
options: {
transform: function(filePath) {
filePath = filePath.replace('/client/app/', '');
filePath = filePath.replace('/client/components/', '');
return '#import \'' + filePath + '\';';
},
starttag: '// injector',
endtag: '// endinjector'
},
files: {
'<%= yeoman.client %>/app/app.scss': [
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}',
'!<%= yeoman.client %>/app/app.{scss,sass}'
]
}
},
// Inject component css into index.html
css: {
options: {
transform: function(filePath) {
filePath = filePath.replace('/client/', '');
filePath = filePath.replace('/.tmp/', '');
return '<link rel="stylesheet" href="' + filePath + '">';
},
starttag: '<!-- injector:css -->',
endtag: '<!-- endinjector -->'
},
files: {
'<%= yeoman.client %>/index.html': [
'<%= yeoman.client %>/{app,components}/**/*.css'
]
}
}
}
...
In my app.scss I just have this:
#import 'settings';
#import 'foundation';
And the _settings file is from the stock Zurb git repo and works without the rest of my application.
When it compiles, I get some css starting with this:
/* Accessibility - hides the forward slash */
[aria-label="breadcrumbs"] [aria-hidden="true"]:after {
content: "/"; }
.icon-bar.two-up .item {
width: 50%; }
.icon-bar.two-up.vertical .item, .icon-bar.two-up.small-vertical .item {
width: auto; }
#media only screen and (min-width: 40.063em) {
.icon-bar.two-up.medium-vertical .item {
width: auto; } }
#media only screen and (min-width: 64.063em) {
.icon-bar.two-up.large-vertical .item {
width: auto; } }
.icon-bar.three-up .item {
width: 33.3333%; }
.icon-bar.three-up.vertical .item, .icon-bar.three-up.small-vertical .item {
width: auto; }
#media only screen and (min-width: 40.063em) {
.icon-bar.three-up.medium-vertical .item {
width: auto; } }
#media only screen and (min-width: 64.063em) {
.icon-bar.three-up.large-vertical .item {
width: auto; } }
.icon-bar.four-up .item {
width: 25%; }
.icon-bar.four-up.vertical .item, .icon-bar.four-up.small-vertical .item {
width: auto; }
#media only screen and (min-width: 40.063em) {
.icon-bar.four-up.medium-vertical .item {
width: auto; } }
#media only screen and (min-width: 64.063em) {
.icon-bar.four-up.large-vertical .item {
width: auto; } }
.icon-bar.five-up .item {
width: 20%; }
.icon-bar.five-up.vertical .item, .icon-bar.five-up.small-vertical .item {
width: auto; }
I'm just learning sass and could do with some pointers. Could someone please help get this working

Related

WEBPACK: imported files (images, fonts...) from scss don't need any additional loader?

Hi webpack experts :) how does my code works without any loader for .ttf or even .png?!
The image is set correctly as background and the font also works!
can anybody explain?
webpack.config.js
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
module: {
rules: [
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
]
},
}
main.scss
#font-face {
font-family: font;
src: url("../assets/fonts/Dosis-Light.ttf");
}
body {
font-family: font;
background-image: url('../assets/images/bg.png');
}

how to add kendo editor tool image which is not in sprite.png

Image shows the last icon which do not have image in sprite.png and i have tried to add font awesome icon image and even it is not working.
I am using kendo-ui 2021 version now.
This is the code I am using and I am unable to add icon image print,clear formation and copy format.
****$("#a").kendoEditor({
imageBrowser: {
transport: {
read: "#Url.Action("Read", "Controllername")",
destroy: {
url: "#Url.Action("Destroy", "Controllername")",
type: "POST"
},
create: {
url: "#Url.Action("Create", "Controllername")",
type: "POST"
},
thumbnailUrl: "#Url.Action("Thumbnail", "Controllername")",
uploadUrl: "#Url.Action("Upload", "Controllername")",
imageUrl: "#Url.Action("Image?path={0}", "Controllername")",
}
},
tools: [
"formatting",
"bold",
"italic",
"underline",
"strikethrough",
"justifyLeft",
"justifyCenter",
"justifyRight",
"justifyFull",
"insertUnorderedList",
"insertOrderedList",
"indent",
"outdent",
"createLink",
"unlink",
"insertImage",
"subscript",
"superscript",
"tableWizard",
"createTable",
"addRowAbove",
"addRowBelow",
"addColumnLeft",
"addColumnRight",
"deleteRow",
"deleteColumn",
"foreColor",
"backColor",
"print"
],
execute: function (e) {
var editor = this;
if (e.name == "createtable") {
setTimeout(function () {
var table = $(editor.body).find("table:not(.custom-table)");
table.addClass("custom-table");
table.attr("style", "border: 1px solid black;");
table.find("tr td")
.each(function () {
var currentStyle = $(this).attr("style");
$(this).attr("style", currentStyle + " border: 1px solid black; ");
});
}, 0);
}
}
});****
I have tried using customer tools in kendo, and insert the relevant images as png and give the image url in style tag.
code inside the kendoEditor:
$("#quest").kendoEditor({
tools[{
name: "print",
tooltip: "print",
exec: function (e) {
}
}
]
styles:
<style>
.k-editor .k-i-print {
background: 50% 50% no-repeat
url(https://localhost/cisiweb2/image/master/print.png);
}
</style>

vuetable-2 - How to fetch data into table in laravel/vuejs usig API

I am trying implementing vuetable-2 in laravel/ vuejs but I don't know how to pass data to the table from api , in sample table we do it like below.
Any help please
methods: {loadBooks() { axios.get("api/Book").then(({ data }) =>
(this.Books = data)); }
mounted() { this.loadBooks(); },
> <tr v-for="Book in Books.data" v-bind:key="Book.id"> <td>{{ Book.name
> }}</td></tr>
here the full code of Book.vue
<template>
<div id="app">
<filter-bar></filter-bar>
<vuetable
ref="vuetable"
api-url="http://vuetable.ratiw.net/api/users"
:fields="fields"
pagination-path=""
:css="css.table"
:sort-order="sortOrder"
:multi-sort="true"
detail-row-component="my-detail-row"
:append-params="moreParams"
#vuetable:cell-clicked="onCellClicked"
#vuetable:pagination-data="onPaginationData"
></vuetable>
<div class="vuetable-pagination">
<vuetable-pagination-info
ref="paginationInfo"
info-class="pagination-info"
></vuetable-pagination-info>
<vuetable-pagination
ref="pagination"
:css="css.pagination"
#vuetable-pagination:change-page="onChangePage"
></vuetable-pagination>
</div>
</div>
</template>
<script>
import accounting from "accounting";
import moment from "moment";
import Vuetable from "vuetable-2/src/components/Vuetable";
import VuetablePagination from "vuetable-2/src/components/VuetablePagination";
import VuetablePaginationInfo from "vuetable-2/src/components/VuetablePaginationInfo";
import Vue from "vue";
import VueEvents from "vue-events";
import CustomActions from "./CustomActions";
import DetailRow from "./DetailRow";
import FilterBar from "./FilterBar";
Vue.use(VueEvents);
Vue.component("custom-actions", CustomActions);
Vue.component("my-detail-row", DetailRow);
Vue.component("filter-bar", FilterBar);
export default {
components: {
Vuetable,
VuetablePagination,
VuetablePaginationInfo
},
data() {
return {
Books: {},
getAllBook: {},
fields: [
{
name: "__sequence",
title: "#",
titleClass: "text-right",
dataClass: "text-right"
},
{
name: "__checkbox",
titleClass: "text-center",
dataClass: "text-center"
},
{
name: "نوم",
sortField: "نوم"
},
{
name: "ایمل",
sortField: "ایمل"
},
{
name: "birthdate",
sortField: "birthdate",
titleClass: "text-center",
dataClass: "text-center",
callback: "formatDate|DD-MM-YYYY"
},
{
name: "nickname",
sortField: "nickname",
callback: "allcap"
},
{
name: "gender",
sortField: "gender",
titleClass: "text-center",
dataClass: "text-center",
callback: "genderLabel"
},
{
name: "salary",
sortField: "salary",
titleClass: "text-center",
dataClass: "text-right",
callback: "formatNumber"
},
{
name: "__component:custom-actions",
title: "Actions",
titleClass: "text-center",
dataClass: "text-center"
}
],
data: [
{
id: 1,
name: "xxxxxxxxx",
nickname: "xxxxxxx",
email: "xxx#xxx.xxx",
birthdate: "xxxx-xx-xx",
gender: "X",
group_id: 1
},
{
id: 50,
name: "xxxxxxxxx",
nickname: "xxxxxxx",
email: "xxx#xxx.xxx",
birthdate: "xxxx-xx-xx",
gender: "X",
group_id: 3
}
],
css: {
table: {
tableClass:
"table table-bordered table-striped table-hover",
ascendingIcon: "glyphicon glyphicon-chevron-up",
descendingIcon: "glyphicon glyphicon-chevron-down"
},
pagination: {
wrapperClass: "pagination",
activeClass: "active",
disabledClass: "disabled",
pageClass: "page",
linkClass: "link",
icons: {
first: "",
prev: "",
next: "",
last: ""
}
},
icons: {
first: "glyphicon glyphicon-step-backward",
prev: "glyphicon glyphicon-chevron-left",
next: "glyphicon glyphicon-chevron-right",
last: "glyphicon glyphicon-step-forward"
}
},
sortOrder: [
{ field: "email", sortField: "email", direction: "asc" }
],
moreParams: {}
};
},
methods: {
loadBooks() {
axios.get("api/Book").then(({ data }) => (this.Books = data));
axios
.get("api/getAllBook")
.then(({ data }) => (this.getAllBook = data));
},
allcap(value) {
return value.toUpperCase();
},
genderLabel(value) {
return value === "M"
? '<span class="label label-success"><i class="glyphicon glyphicon-star"></i> Male</span>'
: '<span class="label label-danger"><i class="glyphicon glyphicon-heart"></i> Female</span>';
},
formatNumber(value) {
return accounting.formatNumber(value, 2);
},
formatDate(value, fmt = "D MMM YYYY") {
return value == null ? "" : moment(value, "YYYY-MM-DD").format(fmt);
},
onPaginationData(paginationData) {
this.$refs.pagination.setPaginationData(paginationData);
this.$refs.paginationInfo.setPaginationData(paginationData);
},
onChangePage(page) {
this.$refs.vuetable.changePage(page);
},
onCellClicked(data, field, event) {
console.log("cellClicked: ", field.name);
this.$refs.vuetable.toggleDetailRow(data.id);
}
},
events: {
"filter-set"(filterText) {
this.moreParams = {
filter: filterText
};
Vue.nextTick(() => this.$refs.vuetable.refresh());
},
"filter-reset"() {
this.moreParams = {};
Vue.nextTick(() => this.$refs.vuetable.refresh());
}
},
mounted() {
this.loadBooks();
},
created() {
this.loadBooks();
}
};
</script>
<style>
.pagination {
margin: 0;
float: right;
}
.pagination a.page {
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 10px;
margin-right: 2px;
}
.pagination a.page.active {
color: white;
background-color: #337ab7;
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 10px;
margin-right: 2px;
}
.pagination a.btn-nav {
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 7px;
margin-right: 2px;
}
.pagination a.btn-nav.disabled {
color: lightgray;
border: 1px solid lightgray;
border-radius: 3px;
padding: 5px 7px;
margin-right: 2px;
cursor: not-allowed;
}
.pagination-info {
float: left;
}
</style>

Kendo Scheduler TimeLineView for Resources, show each event as if its allDay event

I created this Dojo Link that shows me resource by group. problem with this view is it shows long block only if it's a allDay event. for this view how can i apply a template that show a block with time range for each event?
currently it is showing small block and its hard to tell what it is or how long it is booked for.
I want all the event block to take full length despite the duration length.
similar to one highlighted below
Look at the code snippet below. I did a couple of things:
I removed the title data from the template of the event since you said you didn't want to see it.
I placed a space in the template after the <p> element so that the x action of the event will have a place to appear in. There are other ways to do this, I used a simple one.
I've made changes to the styles at the bottom of the code, specifically the following:
.movie-template {
overflow: auto !important;
width: fit-content !important;
}
.k-event {
width: fit-content !important;
}
.space {
width: 20px;
height: 15px;
display: inline-block;
}
.movie-template p {
margin: 5px 0 0;
display: inline-block;
}
EDIT
~~~~
Making the appointments fill the cells they belong to is either impossible or extremely complicated. I'll explain why:
The way the events are programmed, they exist on the same level on the DOM as the table of the scheduler and float above it. The scheduler calculates the position and size of the events based on their start and end times.
In order for you to place them as if they are full-day events you need to do one of two things:
Override the scheduler's automatic placement, with your own code that will calculate the position and size and set them accordingly. Note that this code will need to run every time the window is resized, re-focused, rotated and so on.
Define the events as "whole-day appointments" and store their real start and end times in other fields that you will use in your code. This might break other functionality you might already have in place.
<!DOCTYPE html>
<html>
<head>
<base href="https://demos.telerik.com/kendo-ui/scheduler/resources-grouping-vertical">
<style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style>
<title></title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.221/styles/kendo.common-material.min.css" />
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.221/styles/kendo.material.min.css" />
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.221/styles/kendo.material.mobile.min.css" />
<script src="https://kendo.cdn.telerik.com/2018.1.221/js/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2018.1.221/js/kendo.all.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2018.1.221/js/kendo.timezones.min.js"></script>
</head>
<body>
<div id="example" class="k-content">
<div id="scheduler"></div>
</div>
<script id="event-template" type="text/x-kendo-template">
<div class="movie-template" style="width:100px">
<p>#: kendo.toString(start, "hh:mm") # - #: kendo.toString(end, "hh:mm") #</p>
<span class="space"></span>
</div>
</script>
<script>
var MyCustomTimelistView = kendo.ui.TimelineMonthView.extend({
options: {
name: "MyCustomTimelistView",
title: "Timeline Week",
selectedDateFormat: "{0:D} - {1:D}",
selectedShortDateFormat: "{0:d} - {1:d}",
majorTick: 1440,
numberOfDays: 7
},
name: "MyCustomTimelistView",
calculateDateRange: function() {
//create the required number of days
var start = this.options.date,
// start = kendo.date.dayOfWeek(selectedDate, this.calendarInfo().firstDay, -1),
idx, length,
dates = [];
for (idx = 0, length = this.options.numberOfDays; idx < length; idx++) {
dates.push(start);
start = kendo.date.nextDay(start);
}
this._render(dates);
},
previousDate: function() {
var date = new Date(this.startDate());
date.setDate(date.getDate() - this.options.numberOfDays);
return date
}
});
$(function() {
$("#scheduler").kendoScheduler({
date: new Date("2013/6/13"),
startTime: new Date("2013/6/13 07:00 AM"),
height: 600,
eventTemplate: $("#event-template").html(),
views: [
"day",
{ type: "MyCustomTimelistView", selected: true,
},
"month",
"agenda",
"timeline"
],
timezone: "Etc/UTC",
dataBinding: function(e) {
var view = this.view();
$( ".k-scheduler-times > table > tbody > tr:eq(1)" ).hide();
$( ".k-scheduler-header-wrap > table > tbody > tr:eq(1)" ).hide();
},
dataSource: {
batch: true,
transport: {
read: {
url: "https://demos.telerik.com/kendo-ui/service/meetings",
dataType: "jsonp"
},
update: {
url: "https://demos.telerik.com/kendo-ui/service/meetings/update",
dataType: "jsonp"
},
create: {
url: "https://demos.telerik.com/kendo-ui/service/meetings/create",
dataType: "jsonp"
},
destroy: {
url: "https://demos.telerik.com/kendo-ui/service/meetings/destroy",
dataType: "jsonp"
},
parameterMap: function(options, operation) {
if (operation !== "read" && options.models) {
return {models: kendo.stringify(options.models)};
}
}
},
schema: {
model: {
id: "meetingID",
fields: {
meetingID: { from: "MeetingID", type: "number" },
title: { from: "Title", defaultValue: "No title", validation: { required: true } },
start: { type: "date", from: "Start" },
end: { type: "date", from: "End" },
startTimezone: { from: "StartTimezone" },
endTimezone: { from: "EndTimezone" },
description: { from: "Description" },
recurrenceId: { from: "RecurrenceID" },
recurrenceRule: { from: "RecurrenceRule" },
recurrenceException: { from: "RecurrenceException" },
roomId: { from: "RoomID", nullable: true },
attendees: { from: "Attendees", nullable: true },
isAllDay: { type: "boolean", from: "IsAllDay" }
}
}
}
},
group: {
resources: ["Rooms"],
orientation: "vertical"
},
resources: [
{
field: "roomId",
name: "Rooms",
dataSource: [
{ text: "Meeting Room 101", value: 1, color: "#6eb3fa" },
{ text: "Meeting Room 201", value: 2, color: "#f58a8a" }
],
title: "Room"
},
{
field: "attendees",
name: "Attendees",
dataSource: [
{ text: "Alex", value: 1, color: "#f8a398" },
{ text: "Bob", value: 2, color: "#51a0ed" },
{ text: "Charlie", value: 3, color: "#56ca85" }
],
multiple: true,
title: "Attendees"
}
]
});
});
</script>
<style>
.movie-template {
overflow: auto !important;
width: fit-content !important;
}
.k-event {
width: fit-content !important;
}
.space {
width: 20px;
height: 15px;
display: inline-block;
}
.movie-template img {
float: left;
margin: 0 8px;
}
.movie-template p {
margin: 5px 0 0;
display: inline-block;
}
.movie-template h3 {
padding: 0 0 5px;
font-size: 12px;
}
.movie-template a {
color: #ffffff;
font-weight: bold;
text-decoration: none;
}
.k-state-hover .movie-template a,
.movie-template a:hover {
color: #000000;
}
</style>
</body>
</html>
you can create custom views like this and use it in your code
var CustomMonthView = kendo.ui.TimelineMonthView.extend({
options: {
name: "CustomMonth",
title: "Month"
},
name: "CustomMonth",
_positionEvent: function(eventObject) {
var eventHeight = this.options.eventHeight + 2;
var rect = eventObject.slotRange.innerRect(eventObject.start, eventObject.end, true);
var left = this._adjustLeftPosition(rect.left);
var width = rect.right - rect.left - 2;
if (width < 0) {
width = 0;
}
if (width < this.options.eventMinWidth) {
var slotsCollection = eventObject.slotRange.collection;
var lastSlot = slotsCollection._slots[slotsCollection._slots.length - 1];
var offsetRight = lastSlot.offsetLeft + lastSlot.offsetWidth;
width = this.options.eventMinWidth;
if (offsetRight < left + width) {
width = offsetRight - rect.left - 2;
}
}
eventObject.element.css({
top: eventObject.slotRange.start.offsetTop + eventObject.rowIndex * (eventHeight + 2) + 'px',
left: left,
width: width
});
},
_arrangeRows: function (eventObject, slotRange, eventGroup) {
var startIndex = slotRange.start.index;
var endIndex = slotRange.end.index;
var rect = eventObject.slotRange.innerRect(eventObject.start, eventObject.end, true);
var rectRight = rect.right + this.options.eventMinWidth;
var events = kendo.ui.SchedulerView.collidingEvents(eventObject.slotRange.events(), startIndex, endIndex);
slotRange.addEvent({
slotIndex: startIndex,
start: startIndex,
end: endIndex,
rectLeft: rect.left,
rectRight: rectRight,
element: eventObject.element,
uid: eventObject.uid
});
events.push({
start: startIndex,
end: endIndex,
uid: eventObject.uid
});
var rows = kendo.ui.SchedulerView.createRows(events);
if (eventGroup.maxRowCount < rows.length) {
eventGroup.maxRowCount = rows.length;
}
for (var idx = 0, length = rows.length; idx < length; idx++) {
var rowEvents = rows[idx].events;
for (var j = 0, eventLength = rowEvents.length; j < eventLength; j++) {
eventGroup.events[rowEvents[j].uid].rowIndex = idx;
}
}
}
});
hope this helps

Grouping classes with similar rules in SASS

I'm using the following algorithm to create column classes in SASS:
$columns: 8;
$exclude-columns: 5 7;
#for $i from 1 through $columns {
#for $j from 1 through $i {
$width: (100% / $i) * $j;
#if $i != $j and index($exclude-columns, $i) {
.layout-unit-#{$j}of#{$i} {
width: $width;
}
}
}
}
It's working fine, however, the output has quite a bit of duplication:
.layout-unit-1of2 { width: 50%; }
.layout-unit-1of3 { width: 33.33333%; }
.layout-unit-2of3 { width: 66.66667%; }
.layout-unit-1of4 { width: 25%; }
.layout-unit-2of4 { width: 50%; }
.layout-unit-3of4 { width: 75%; }
.layout-unit-1of6 { width: 16.66667%; }
.layout-unit-2of6 { width: 33.33333%; }
.layout-unit-3of6 { width: 50%; }
.layout-unit-4of6 { width: 66.66667%; }
.layout-unit-5of6 { width: 83.33333%; }
.layout-unit-1of8 { width: 12.5%; }
.layout-unit-2of8 { width: 25%; }
.layout-unit-3of8 { width: 37.5%; }
.layout-unit-4of8 { width: 50%; }
.layout-unit-5of8 { width: 62.5%; }
.layout-unit-6of8 { width: 75%; }
.layout-unit-7of8 { width: 87.5%; }
Is there a way to get the output to look more like this:
.layout-unit-1of2,
.layout-unit-2of4,
.layout-unit-3of6,
.layout-unit-4of8 { width: 50%; }
.layout-unit-1of3,
.layout-unit-2of6 { width: 33.33333%; }
.layout-unit-2of3,
.layout-unit-4of6 { width: 66.66667%; }
.layout-unit-1of4,
.layout-unit-2of8 { width: 25%; }
.layout-unit-3of4,
.layout-unit-6of8 { width: 75%; }
.layout-unit-1of6 { width: 16.66667%; }
.layout-unit-5of6 { width: 83.33333%; }
.layout-unit-1of8 { width: 12.5%; }
.layout-unit-3of8 { width: 37.5%; }
.layout-unit-5of8 { width: 62.5%; }
.layout-unit-7of8 { width: 87.5%; }
Or is this a limitation of SASS?
It's not a limitation of Sass, but the algorithm.
Here is a solution that requires at least Sass 3.3 (see live demo on SassMeister):
Note: I fixed your code to support the exclusion of columns.
$columns: 8;
$exclude-columns: 5 7;
// A stack to store the different widths.
$width_stack: ();
#for $i from 1 through $columns {
#for $j from 1 through $i {
#if $i != $j and not index($exclude-columns, $i) {
$width: (100% / $i) * $j;
// Compute the number 66.66667% to a valid CSS selector: "66-66667".
$width_unitless: $width / 1% + unquote("");
$width_dot: str-index($width_unitless, '.');
#if $width_dot {
$width_unitless: str-slice($width_unitless, 0, $width_dot - 1) +
"-" +
str-slice($width_unitless, $width_dot + 1);
}
// Manage the stack of known widths to avoid repeats.
#if not index($width_stack, $width_unitless) {
$width_stack: append($width_stack, $width_unitless);
// Dynamic placeholder!
%mycols-#{$width_unitless} {
width: $width;
}
}
.layout-unit-#{$j}of#{$i} {
#extend %mycols-#{$width_unitless};
}
}
}
}

Resources