Next and Prev buttons for pages in the same folder in nanoc - nanoc

I'm building a static generator for a getting started guide and I am grouping pages for the same guide into a folder.
how would I add a footer to the bottom of the generated pages for next and previous pages?

Pages (“items“) in Nanoc do not have an intrinsic ordering, and there is thus no concept of “previous” and “next” unless you add one. There are ways to introduce an ordering to a list of items, though:
Sort the items by title (#items.sort_by { |i| i[:title] })
Sort the items by an ordering attribute (#items.sort_by { |i| i.fetch(:order, 0) })
Define an ordering in a configuration file (this is how nanoc.ws does it)
…
Once there is an ordering defined for a collection of items, you can use #index to find out the index of the current page, and thus also the indices of the previous and next pages. For example, assuming that ordered_items is the list of ordered items:
def previous_item(item)
idx = ordered_items.index(item)
if idx && idx > 0
ordered_items[idx - 1]
end
end
A #next_item function would be similar.
How the #ordered_items function is implemented, depends on what you would like to achieve. Do you want to order all items on the site? If so, an implementation could look like this:
def ordered_items
#items.select { |i| i.key?(:order) }.sort_by { |i| i[:order] }
end
If, on the other hand, you’d prefer the ordered items to be scoped to a certain section of the site, you could have an implementation like
def ordered_items_for_manual
#items.find_all('/manual/**/*')
.select { |i| i.key?(:order) }
.sort_by { |i| i[:order] }
end
With the ChildParent helper, you could determine the ordered items for the current section automatically:
def ordered_siblings_and_self
children_of(parent_of(#item))
.select { |i| i.key?(:order) }
.sort_by { |i| i[:order] }
end
Finally, in the footer (e.g. in the default layout), you could stick something like
<% if previous_item %>
<%= link_to(previous_item[:title], previous_item) %>
<% end %>
and then you’d have a link to the previous page. Similar for the next link.

Related

keep footer last in list when dragging new item into the list from another

I have Vue Draggable working like a Kanban board with multiple columns, and I have made each column at least the height of the viewport so that each item can easily be dragged into the column next to it (for cases where one column is much longer than the next, for example).
I also have a button in the footer slot to add new cards to the column. This works well in that it is generally always at the bottom of the list, is not draggable, etc.
The issue arises when I drag an item from another list in below the footer (but still within the height of the draggable element). When I do this, the footer does not stay below the new item, which looks odd.
Once I drop the element, it snaps into place and the footer is one again at the bottom - it is only when the new card is being moved that it appears below the footer.
Is there any way to make sure that even during the move event and a new card being added to a list that the footer stays as the last element?
This issue seems to be captured in this comment on Github issues - https://github.com/SortableJS/Vue.Draggable/issues/673#issuecomment-554149705 - but no solution is provided in that thread.
Any help greatly appreciated.
So for anyone having the same issue I solved this by doing the following:
Hooking into the onMove event using the :move prop to run a function each time an item is moved... e.g. :move="fixFooter"
Creating a function that checks if the moved item would be the last in the new list (thus by default it would sit below the footer without this fix), and if so on the $nextTick append the footer to the Draggable container so it gets moved below the item coming in to the list... (just learnt that .appendChild will move an existing element to the end)
It then checks if the from/to list is the same to account for the item being dragged back into the list it's coming from during the same drag event (otherwise you get the same undesirable footer behaviour as before if you drag an item out of a list and back below the footer without dropping it).
fixFooter(e) {
if(e.relatedContext.list.length === e.draggedContext.futureIndex) {
var newListDom = e.relatedContext.component.$el;
var newListFooter = e.relatedContext.component.$slots.footer[0].elm;
this.$nextTick(() => {
newListDom.appendChild(newListFooter);
});
} else if(e.from === e.to && (e.from.children.length - 1) === e.draggedContext.futureIndex) {
var currentListDom = e.from;
var currentListFooter = e.from.lastChild;
this.$nextTick(() => {
currentListDom.appendChild(currentListFooter);
});
}
}
Seems like for vue draggable next this is enough:
fixFooter(e) {
if (e.relatedContext.list.length === e.draggedContext.futureIndex) {
var newListDom = e.to
var newListFooter = e.to.lastChild
this.$nextTick(() => {
newListDom.appendChild(newListFooter);
});
}
}

Hide empty section in Compositional Layout Collection View with DiffableDataSource not working

I have three sections of data on screen: one for Five Star Items, one for Visits, and one for Other Fun Places items. They are all of the same type: funPlaces. For this example, I'm creating three arrays of funPlaces and showing them in three sections.
What I'd like to do is not generate a section on-screen at all if there is no data available for that section. So if there is no Visits data, I only want to see the sections for Five Star Items and Other Fun Places items. If there are no Five Star Items, I only want to see the sections for Visits and Other Fun Places items, etc.
However...
What's happening is that if there are no Visits items for example, rather than the Visits section disappearing as I would expect, instead the data from otherFunPlaces gets shown in the Visits section on-screen and the entire Other Fun Places section disappears. Yes, the Visits data is gone, but the section is still there and its data has been replaced with Other Fun Places data on-screen.
If I have an empty Five Star Items array, the Five Star Items section is still shown but the data has been replaced with Visits data on-screen. And the Other Fun Places data shifts up a section to the Visits Section--it's the Other Fun Places section that actually disappears on-screen.
The only case that works is if I have an empty Other Fun Items array--the section disappears as expected, and the data in the two sections above is also in its proper place on-screen.
What am I missing to make this work?
func applySnapshot(animatingDifferences: Bool = true) {
let funPlaces = fetchedResultsController.fetchedObjects ?? []
if funPlaces.count > 6 {
// we only generated enough sample data objects in viewDidLoad for this test
// we're going to make sure there are no fiveStarItems in the array for this test
// let fiveStarItems = Array<FunPlace>(funPlaces[0...1])
let fiveStarItems: [FunPlace] = [] // hide fiveStarItems
// we're going to keep data in visitsItems and otherItems
let visitsItems = Array<FunPlace>(funPlaces[2...3])
let otherFunPlacesItems = Array<FunPlace>(funPlaces[4...5])
var snapshot = NSDiffableDataSourceSnapshot<Sections, FunPlace>()
if !fiveStarItems.isEmpty {
snapshot.appendSections([Sections.fiveStar])
snapshot.appendItems(fiveStarItems, toSection: .fiveStar)
}
if !visitsItems.isEmpty {
snapshot.appendSections([Sections.visits])
snapshot.appendItems(visitsItems, toSection: .visits)
}
if !otherFunPlacesItems.isEmpty {
snapshot.appendSections([Sections.otherFunPlaces])
snapshot.appendItems(otherFunPlacesItems, toSection: .otherFunPlaces)
}
dataSource?.apply(snapshot, animatingDifferences: true)
}
}
Thanks.
When creating the layout make sure you do it based on the section identifier, not the section number. This way, any section with no data will not be displayed:
let layout = UICollectionViewCompositionalLayout(sectionProvider: { sectionNumber, env in
let sectionIdentifier = self.dataSource.snapshot().sectionIdentifiers[sectionNumber]
if sectionIdentifier == .fiveStar {
return FiveStarSectionLayout....
} else if (sectionIdentifier == .visits) {
return VisitsSectionLayout....
} else if sectionIdentifier == .otherFunPlaces) {
return OtherFunPlacesSectionLayout...
} else {
return nil
}
})
self.collectionView.setCollectionViewLayout(layout, animated: true)
I figured it out.
The problem was indexing the sections to lay out. I had to keep track of available sections separately and only lay out the ones available.

While loops in EJS display on a single line

I have rendered data into EJS and now using a while loop I want to cycle through that data and display it on a single line. For example let's say the data is an array of objects with name attributes. I have the names ['Bob','Chris','Sarah']. After I have sent the data over to EJS I want Bob to appear, and then after some time Bob disappears and then Chris appears and then Sarah appears.
As of right now my code outputs them all at once over multiple lines, instead of one at a time on a single line as I desire.
<body>
<% var current = 0; %>
<% while (current < 2){ %>
<h1> Hey <%= person[current].name %>, how are you doing? </h1>
<% current += 1;
};%>
</body>
The output should be on a single line: Hey CURRENT NAME, how are you doing? Then the current name should just keep changing.
Instead the code outputs three lines, one for each name. Does anybody know how to fix this? Thanks.
I think you are confusing template processing (which produces static output) with dynamic updating of a DOM element. The latter is more of a problem for a front-end framework (although just what you have provided is simple enough in vanilla JS).
I would look into just creating a template with a placeholder for the name and then use a timer to update the text inside:
setInterval(3000, function(){
var span = document.getElementById('name-span');
if ('textContent' in span) {
span.textContent = person[current].name;
} else {
span.innerText = person[current].name;
}
current = (current + 1) % person.length;
}
Of course you need to figure out a better way to access person and current instead of using globals.

Using composed xpath to locate an element and click on it

I am trying to retrieve a list of elements using XPATH and from this list I want to retrieve a child element based on classname and click it.
var rowList = XPATH1 + className;
var titleList = className + innerHTMLofChildElement;
for(var i = 0; i < titleList.length; i++) {
if(titleList[i][0] === title) {
browser.click(titleList[i][0]); //I don't know what to do inside the click function
}
}
I had a similar implementation perhaps to what you are trying to do, however my implementation is perhaps more complex due to using CSS selectors rather than XPath. I'm certain this is not optimized, and can most likely be improved upon.
This uses the methods elementIdText() and elementIdClick() from WebdriverIO to work with the "Text Values" of the Web JSON Elements and then click the intended Element after matching what you're looking for.
http://webdriver.io/api/protocol/elementIdText.html
http://webdriver.io/api/protocol/elementIdClick.html
Step 1 - Find all your potential elements you want to work with:
// Elements Query to return Elements matching Selector (titles) as JSON Web Elements.
// Also `browser.elements('<selector>')`
titles = browser.$$('<XPath or CSS selector>')
Step 2 - Cycle through the Elements stripping out the InnerHTML or Text Values and pushing it into a separate Array:
// Create an Array of Titles
var titlesTextArray = [];
titles.forEach(function(elem) {
// Push all found element's Text values (titles) to the titlesTextArray
titlesTextArray.push(browser.elementIdText(elem.value.ELEMENT))
})
Step 3 - Cycle through the Array of Title Texts Values to find what you're looking for. Use elementIdClick() function to click your desired value:
//Loop through the titleTexts array looking for matching text to the desired title.
for (var i = 0; i < titleTextsArray.length; i++) {
if (titleTextsArray[i].value === title) {
// Found a match - Click the corresponding element that
// it belongs to that was found above in the Titles
browser.elementIdClick(titles[i].value.ELEMENT)
}
}
I wrapped all of this into a function in which i provided the intended Text (in your case a particular title) I wanted to search for. Hope this helps!
I don't know node.js, but in Java you should achieve your goal by:
titleList[i].findElementBy(By.className("classToFind"))
assuming titleList[i] is an element on list you want to get child elements from

Watir-WebDriver: Find elements which class is not 'completed'

I have a bunch of li elements. They are either with uncompleted, 'current' or completed class or without a class.
How do I find all li elements without completed class?
So far I am doing this by selecting necessary li objects from collection of li (through calling #attribute_value('class'), but maybe there is some elegant locating strategy in Watir-WebDriver?
UPD: As long as there is some misunderstanding of the question.
I want to know if there is locating strategy within Watir-WebDriver to find elements which class is not completed.
I know I can do this with Ruby and doing it like this:
browser.lis.select do |li|
li.attribute_value('class') != 'completed'
end
But the question is if I can do this in one line by passing some argument to #lis
UPD2: Just realized that given class names narrows solutions. Updated question and sorry for that.
The LI collection supports locators, which means you can do:
browser.lis(:class, 'uncompleted').each{ |x|
puts x.text
}
UPDATE: For the case where there are multiple classes, you can modify the above to use a regex to check for not completed:
browser.lis(:class, /^(?!completed$)/).each{ |x| puts x.class_name }
This returns all li that have no class or are not exactly 'completed' (ex 'completed2').
Note: I think .class_name may have better support than attribute_value('class') (which I believe does not work in IE as it needs to be className).
In order to not assume that there are only two classes of arrays, you can do:
all = browser.lis.collect { |li| li.class }
completed = browser.lis(:class, 'completed').collect { |li| li.class }
not_completed = (all - completed)
or even:
all = browser.lis.collect { |li| li.class }
not_completed = Array.new
all.each do |li|
if li.class != "completed"
not_completed << li
end
end

Resources