Truncating paragraph text with a fixed hight and no bleed - height

See attached image. (http://i.imgur.com/SWlUllK.jpg)
I have three adjacent paragraphs which I would like to truncate before the "Read More" call-to-action.
The catch is that each of the articles has a fixed height so that my truncation buttons ("Read More") can line up on the same horizontal as you can see in the image.
The problem I'm facing here is that the length of the excerpt is dependant on the length of the heading text. If there is a three line heading then the length of the excerpt will be shorter due to the increased height that the header is taking up.
At the moment I'm using some jQuery to do character count and truncate after X words. It's occurred to me that that is not a viable solution.
I couldn't figure out a way to use CCS either because the button at the bottom is absolutely positioned so overflow:hidden ignores the height of the button and just flows behind it cutting it off at the end of the article.
The only thing that I've thought of is forcing the article to be a specific height and then moving the "Read More" button outside the article tag but that doesn't seem like good semantics to me.
Any thoughts on this?
Sass:
article {
border-right: 1px solid #e7e7e7;
height: 506px;
position: relative;
p {
font-size: emCalc(16px);
overflow: hidden;
margin-bottom: 0.6em;
max-height: 255px;
}
p + a {
position: absolute;
bottom: 0;
display: block;
background: $lightBlue;
padding: 0.8em;
color: #fff;
text-align: center;
}
}

I good way of doing it is having "overflow-y: scroll" on your article HTML element and then removing text until the ".scrollHeight == .offsetHeight" of the HTML element.
http://jsfiddle.net/qdxTj/
Here's the straight JavaScript.
var all = document.getElementsByTagName("div");
for(var i=0; i<all.length; i++) {
var article = all[i];
if(article.className && /(^|\s)article($|\s)/.test(article.className)) {
article.scrollTop = 1;
var cnt = 0;
while(article.scrollTop != 0 || article.scrollHeight != article.offsetHeight) {
cnt++; if(cnt > 50) break;
var ps = article.getElementsByTagName("p");
if(ps.length == 0)
break;
var p = ps[ps.length - 1];
var shorter = p.innerHTML;
var idx = shorter.lastIndexOf(" ");
shorter = idx >= 0 ? shorter.substring(0, idx) : "";
p.innerHTML = shorter;
if(p.innerHTML.length == 0)
article.removeChild(p);
article.scrollTop = 1;
}
article.style.overflowY = "hidden";
}
}

Related

Endless/Carousel horizontal(Lazyrow) scrollview

I have made a lazyrow (horizontal) scroll with 10 items. My problem now is that when I scroll to item number 10, it stops. And I want it to go back to item number 1 again like a carousel. Does anyone know this? Here is my code for the 10 items:
//Horizontal Scroll view
item {
LazyRow {
items(10) { count->
Card(
modifier = Modifier
.width(110.dp)
.height(140.dp)
.padding(10.dp, 5.dp, 5.dp, 0.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.White),
elevation = 5.dp
) {
Column(
modifier = Modifier.padding(5.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = hi.imageId),
contentDescription = "profile Image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(60.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.padding(5.dp))
Text(
text = hi.talk,
color = Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 16.sp)
}
}
}
}
}
I'm pretty new to kotlin, so it is taking some fair time to learn. I couldn't find anything related to this in Jetpack Compose, and it is hard for me to convert from Java.

Blazor ChartJs: Expanding bar to the end of the canvas

Using the Blazor.ChartJs library, I have a series of horizontal stacked bar charts, the purpose of which is to show the percentage complete. The red portion is the yearly goal and the blue is actual number achieved so far:
The first chart could have a goal of $10,000,000, the second $3,000,000, the third $7,000,000, etc. But I would like each chart to expand to the end of the table cell in which it is contained because I don't care about the numbers, just the percentages:
As you can tell in the first chart, the numbers are stopping before the end of the cell. I am adding the charts to HTML table cells, one for each statistic I'm tracking:
<table>
<tbody>
<tr>
<td></td>
<td>% Complete</td>
</tr>
#for (int i = 0; i < typeOfProject.Count; i++)
{
<tr>
<td><Chart Config="_configs[i]" Height="8" /></td>
<td>#String.Format("{0:P0}", percentagesComplete[i])</td>
</tr>
}
</tbody>
</table>
I build the _configs collection using a method that returns a BarConfig options, and the configurations are identical for all charts. I have experimented with the Responsive and MaintainAspectRatio properties, but this does not seem to affect the results.
config = new BarConfig
{
Options = new BarOptions
{
MaintainAspectRatio = true,
Responsive = true,
Legend = new Legend
{
Display = false
},
Scales = new BarScales
{
XAxes = new List<CartesianAxis>
{
new BarLinearCartesianAxis
{
Stacked = true,
OffsetGridLines = false,
Offset = false,
Display = AxisDisplay.False
}
},
YAxes = new List<CartesianAxis>
{
new BarCategoryAxis
{
Stacked = true,
OffsetGridLines = false,
Offset = false,
Display = AxisDisplay.False,
BarThickness = 45
}
}
}
}
I have also add a div tag around the Canvas object in each cell as mentioned in this question, but that wasn't the solution to my problem.
How can I expand the bars to the end of the canvas in each cell?
I created my own code to do this because I could not get the chart width to consistently expand to the end of the table cell. The requirements changed slightly as well, so I needed to draw an empty rectangle representing 100% of the goal and then filling the rectangle with a percentage of how complete the goal was.
First, I have a JavaScript method that gets the width of the canvas, calculates how many pixels to draw based on that width and the "value" variable (which is a percentage), then uses the context to first draw a clear rectangle. It draws a filled rectangle representing the progress to the goal:
function buildCanvas(canvasId, value) {
var canvasWidth = document.getElementById(canvasId).offsetWidth;
var calculatedWidth = parseInt(canvasWidth * value);
var canvasToDraw = document.querySelector('#' + canvasId);
const ctx1 = canvasToDraw.getContext('2d');
ctx1.clearRect(0, 0, 300, 150);
ctx1.fillStyle = 'rgba(173, 116, 76, 1.0)';
ctx1.fillRect(0, 0, calculatedWidth, 150);
}
I define the chart that I want to draw:
<canvas id="chart" style="border: 1px solid #AD744C; height: 18px; width: 300px">
I then inject IJSRuntime and call the JavaScript method to draw the rectangle, the value being the goal divided by the value to get a percentage:
#inject IJSRuntime JsRuntime
...
JsRuntime.InvokeAsync<string>("buildCanvas", $"chart", $"{value}");

How to color table rows with 3 alternate color

I want a table to display with 3 alternate colors (1-black,2-red,3-white,4-black, 5-red,6-white ....) i tried with nth-child(even) and nth-child(odd)
but how to get alternate 3 row colors
Following to the w3c, you try this:
tr:nth-child(3n+1) {
background-color: black;
}
tr:nth-child(3n+2) {
background-color: red;
}
tr:nth-child(3n) {
background-color: white;
}

Display one polyline of leaflet map with two different colors in shiny

Is there any way to display one polyline with two different colours(ex: based on road speed limit) in leaflet by using shiny? Any help could be highly appreciated.
You can use this plugin : http://hgoebl.github.io/Leaflet.MultiOptionsPolyline/demo/
You will have to define the speed for each point in your GeoJSON properties.
Then you simply have to add this code after :
var myPolyline = L.multiOptionsPolyline(YourGeoJSONHere, {
multiOptions: {
optionIdxFn: function (latLng) {
var i,
speedThresholds = [5, 10, 15, 30];
for (i = 0; i < speedThresholds.length; ++i) {
if (latLng.alt <= speedThresholds[i]) {
return i;
}
}
return speedThresholds.length;
},
options: [
{color: '#0000FF'}, {color: '#0040FF'},
{color: '#0080FF'}, {color: '#00FFB0'}
]
},
weight: 5,
opacity: 0.9,
smoothFactor: 1
}).addTo(layerTrace);
For the speed : https://github.com/hgoebl/Leaflet.MultiOptionsPolyline/blob/master/demo/js/demo.js#L59-L80

Swift - Add attributed string to items in parenthesis

I'm trying to display text to a view from a set of definitions and in cases where that text/string has parenthesis, I would like to display that parenthesis element in a different way - let's say bolded vs non bolded.
"This string a"
"This string b (has parenthesis)" - parenthesis show in lower
weight font
Now I'm aware that the solution is found by combining regular expressions - \(\w*\) - with attributed strings, but I haven't been able to combine it meaningfully.
This is my function that prints the words
func setWord(_ index:Int) {
if (index < 0) { return }
let word:[String:AnyObject] = self.definitions[index]
if let wordLabelText = word[self.store.sourceLanguage.lowercased()] as? String {
self.wordLabel.attributedText = NSMutableAttributedString(string: wordLabelText, attributes: [NSKernAttributeName: 1.0])
}
print(word)
self.definitionView.word = word
}
What I've done so far is added some more definitions for the attributed strings output, but then I'm not sure how to continue:
func setWord(_ index:Int) {
if (index < 0) { return }
let font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
let fontSize = font.pointSize * 3
let plainFont = UIFont(name: "X-BoldItalic", size: fontSize)
let boldFont = UIFont(name: "X-Italic", size: fontSize)
let word:[String:AnyObject] = self.definitions[index]
if let wordLabelText = word[self.store.sourceLanguage.lowercased()] as? String {
self.wordLabel.attributedText = NSMutableAttributedString(string: wordLabelText, attributes: [NSKernAttributeName: 1.0, NSFontAttributeName: boldFont])
Here I'd need to loop through the words looking for the (elements), but I'm not sure how to do that and properly return the words. Am I on the right path? Thanks a bunch :)
A solution in Objective-C, with explained logic that should be easily translated in Swift.
NSDictionary *defaultAttributes = #{NSFontAttributeName: [UIFont boldSystemFontOfSize:15]};
NSDictionary *otherAttributes = #{NSFontAttributeName: [UIFont systemFontOfSize:12]};
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\(.*?\\)" options:0 error:nil];
NSString *initialString = #"This string a (has parenthesis), This string b (has parenthesis too), This string C hasn't.";
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:initialString attributes:defaultAttributes];
NSLog(#"Attr: %#", attr);
NSArray *allMatches = [regex matchesInString:[attr string] options:0 range:NSMakeRange(0, [attr length])];
for (NSTextCheckingResult *aResult in allMatches)
{
[attr addAttributes:otherAttributes range:[aResult range]];
}
NSLog(#"Attr: %#", attr);
Logs:
$> Attr: This string a (has parenthesis), This string b (has parenthesis too), This string C hasn't.{
NSFont = "<UICTFont: 0x14663df0> font-family: \".SFUIText-Semibold\"; font-weight: bold; font-style: normal; font-size: 15.00pt";
}
$> Attr: This string a {
NSFont = "<UICTFont: 0x14663df0> font-family: \".SFUIText-Semibold\"; font-weight: bold; font-style: normal; font-size: 15.00pt";
}(has parenthesis){
NSFont = "<UICTFont: 0x14666260> font-family: \".SFUIText\"; font-weight: normal; font-style: normal; font-size: 12.00pt";
}, This string b {
NSFont = "<UICTFont: 0x14663df0> font-family: \".SFUIText-Semibold\"; font-weight: bold; font-style: normal; font-size: 15.00pt";
}(has parenthesis too){
NSFont = "<UICTFont: 0x14666260> font-family: \".SFUIText\"; font-weight: normal; font-style: normal; font-size: 12.00pt";
}, This string C hasn't.{
NSFont = "<UICTFont: 0x14663df0> font-family: \".SFUIText-Semibold\"; font-weight: bold; font-style: normal; font-size: 15.00pt";
}
What is the idea:
Create a NSMutableAttributedString with the "default" attributes (in our case bold font with "big size").
Then create a NSRegularExpression and find all occurrences.
You'll add the attributes (in our case small and normal font) at that occurrence range.
In our case, it works simply, because since you can only have one attribute per kind maximum at a specific range, the NSFontAttributeName attribute will be replaced for that range.
If you added more attributes, and want to remove them, you may need to not call addAttributes:range:, but replaceCharactersInRange:withAttributedString: instead:
NSAttributedString *replacement = [[NSAttributedString alloc] initWithString:[[attr string] substringWithRange:[aResult range]]
attributes:otherAttributes];
[attr replaceCharactersInRange:[aResult range] withAttributedString:replacement];
Edit: Swift 3 Version
Nota Bene: I'm clearly not a Swift Developer, this code seems to work, but clearly, I "write" Swift like I write Objective-C, and many things since I don't use them daily and didn't read the doc are wrongly done (like the conversion/cast/explicit type/class, the "as", the "!", the "?", etc.), but it could be a start for you.
If you are a Swift developer and spots issues, feel free to comment the post and suggest your modifications. If you're just here because you have the same issue, don't forget to read in the comment if there are more Swifty things to fix in my pseudo code.
let defaultAttributes:[String:Any] = [NSFontAttributeName:UIFont.boldSystemFont(ofSize:15)];
let otherAttributes:[String:Any] = [NSFontAttributeName:UIFont.systemFont(ofSize:12)];
do {
let regex = try NSRegularExpression.init(pattern: "\\(.*?\\)", options: [])
let initialString:String = "This string a (has parenthesis), This string b (has parenthesis too), This string C hasn't."
let attr = NSMutableAttributedString.init(string: initialString, attributes: defaultAttributes)
print("Attr\(attr)");
let allMatches:[NSTextCheckingResult] = regex.matches(in: attr.string, options:[], range: NSRange(location: 0, length: attr.string.characters.count))
for aResult in allMatches
{
let occurrence = (attr.string as NSString).substring(with: aResult.range)
let replacement = NSAttributedString.init(string: occurrence , attributes: otherAttributes)
attr.replaceCharacters(in: aResult.range, with: replacement)
//attr.addAttributes(otherAttributes, range: aResult.range)
}
print("Attr\(attr)");
} catch let regexError {
print(regexError)
}