How to store changes to a page in tornado.no/cms? - groovy

In a controller for a page in tornado-cms, I do the following:
def res = Service.tornado.articles([ articleCategoryPath: "boker/ny"]);
Service.tornado.loadFullArticles(res);
res.sort { a,b ->
b.props.year <=> a.props.year
}
tornado.small_articles = res;
Or, shorter:
tornado.small_articles = Service.tornado.articles([
articleCategoryPath: "boker/ny",
full: true ])
.sort { a, b -> b.props.year <=> a.props.year };
This fills the content box small_articles with the all the articles from a specific folder "boker/ny" reversely sorted by the article prop year.
It works fine, but is it possible to save the changes made to the content box tornado.small_articles so that the resulting list of articles is also visible from the GUI? Something like Service.tornado.saveChangesToPageContentBox(page, content_box_nr, tornado.small_articles);?

Start by removing the articles currently bound to the small_articles box like this:
Integer containerId = <id-of-small-articles-container>;
Service.tornado.getPageBindings(page.id).each { binding ->
if (binding.containerId == containerId)
Service.tornado.deletePageBinding(binding);
}
Then add new bindings for the articles you have collected:
tornado.small_articles.each {
Service.tornado.addPageBinding(new ArticlePageContainer(page.id, it.id, containerId));
}
You should not do this on every request to the given page, but rather update the list when the content changes or by some other, less frequent criteria.

Related

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.

Compare two text files using Groovy

Compare two text files using groovy in Jenkins slave machine. Sample text files are as follows.
Sample1.txt:
team_a, added
team_b, removed
team_c, added
Sample2.txt:
team_d, added
team_e, added
team_c, removed
Need to identify the change and should give the output as team_c has been removed.
Asking this question as being newbie to groovy.
You haven't posted your code, but say you are a newbie, so I'll talk you through an example:
// For simplicity, I won't use files, but a "heredoc"
Sample1txt = '''
team_a, added
team_b, removed
team_c, added
'''
Sample2txt ='''
team_d, added
team_e, added
team_c, removed
'''
//get the data in a map each
Map a = [:]
Sample1txt.splitEachLine(",") { line -> a[line[0]] = line[1]}
Map b = [:]
Sample2txt.splitEachLine(",") { line -> b[line[0]] = line[1]}
//for each key value pair in the second map...
b.each {k, v ->
//if the key exists (you didn't say what you want to do with new keys)
if (a.containsKey(k)) {
//and if the key's value is not equal to the same key's value in the first map
if (!b[k].equals(a[k])) {
//print it
println "${k} : ${v}"
}
}
}
This is not a robust solution, but it will point you in the right direction.

Hyperlink to page with smart search filter prepopulated

I have a smart search page that shows all the products on the page with some smart search filters that narrows down the products on some criterias (Let's say for example Filter1 has Option1, Option2 and Option3).
What I am trying to accomplish is to have a link on a seperate page that links to the product page, but when the user clicks on that link some of the search filters gets set (For example Filter1 would have Option2 selected).
I'm not sure if that is possible with out of the box solution, but with simple tweaks inside SearchFilter.ascx.cs, you can make a workaround. File is placed under CMSWebParts/SmartSearch/SearchFilter.ascx.cs. You should change method 'GetSelectedItems' to take a look into query string for filter value (see snippet bellow):
/// <summary>
/// Gets selected items.
/// </summary>
/// <param name="control">Control</param>
/// <param name="ids">Id's of selected values separated by semicolon</param>
private string GetSelectedItems(ListControl control, out string ids)
{
ids = "";
string selected = "";
//CUSTOM: retrive value for query string
var customFilter = QueryHelper.GetString("customFilter", "");
// loop through all items
for (int i = 0; i != control.Items.Count; i++)
{
//CUSTOM: ----START-----
if (!RequestHelper.IsPostBack())
{
if (!string.IsNullOrEmpty(customFilter))
{
if (control.Items[i].Text.Equals(customFilter, StringComparison.InvariantCultureIgnoreCase))
{
control.Items[i].Selected = true;
}
}
}
//CUSTOM: ----END-----
if (control.Items[i].Selected)
{
selected = SearchSyntaxHelper.AddSearchCondition(selected, control.Items[i].Value);
ids += ValidationHelper.GetString(i, "") + ";";
}
}
if (String.IsNullOrEmpty(selected) && (control.SelectedItem != null))
{
selected = control.SelectedItem.Value;
ids = control.SelectedIndex.ToString();
}
return selected;
}
And your hyperlink will look like this: /Search-result?searchtext=test&searchmode=anyword&customfilter=coffee
With this modifications, you can send only one value in filter, but if you need more then one value, you can send them and customize it however suits you best. Also, you can send filter name (in case that you have multiple filters) and then add check in method above.
I will recommend you not to modify kentico files. Instead of that, clone default filter web part and make modifications there, because withing next upgrade of project, you will lose your changes. I checked this in Kentico 11.
For Smart Search Filters:
if turn off auto-post back option -then web part control ID should become a query string parameter that you can use.
This above will form something like:
/Smart-search-filter.aspx?searchtext=abc&searchmode=anyword&wf=2;&ws=0;&wa=0
P.S. I suggest you to take a look at the corporate site example: look the smart search filter web part: /Examples/Web-parts/Full-text-search/Smart-search/Smart-search-filter. It is working example you can use it as starting point.

The ability to create Doc element of hypertext in Websharper.UI.Next

I have the string with html markup, an I want to cretae Doc elment from it like this:
Doc.FromHtm "<div><p>.....</p>.....</div>"
As I understand that this is not possible right now. Ok, what is not possible to accurately sew, I tried to roughly nail using jquery:
JQuery.JQuery.Of( "." + class'name ).First().Html(html'content)
But to call this code, I need to specify an event handler for the Doc element. But it is not implemented in UI.Next.
I tried to track changes of a model with a given CSS class asynchronously:
let inbox'post'after'render = MailboxProcessor.Start(fun agent ->
let rec loop (ids'contents : Map<int,string>) : Async<unit> = async {
// try to recive event with new portion of data
let! new'ids'contents = agent.TryReceive 100
// calculate the state of the agent
let ids'contents =
// merge Map's
( match new'ids'contents with
| None -> ids'contents
| Some (new'ids'contents) ->
new'ids'contents # (Map.toList ids'contents)
|> Map.ofList )
|> Map.filter( fun id content ->
// calculate CSS class name
let class'name = post'text'view'class'name id
// change it's contents of html
JQuery.JQuery.Of( "." + class'name ).First().Html(content).Size() = 0)
// accept the state of the agent
return! loop ids'contents }
loop Map.empty )
and then, for example for one element:
inbox'post'after'render.Post [id, content]
But it is too difficult, unreliable and not really working.
Please give me an idea how to solve the problem if possible. Thanks in advance!
Just in case someone needs to use static HTML in WebSharper on the server (I needed to add some javascript to the WebSharper generated HTML page), there is fairly new Doc.Verbatim usable e.g. like
let Main ctx action title body =
Content.Page(
MainTemplate.Doc(
title = title,
menubar = MenuBar ctx action,
body = body,
my_scripts = [ Doc.Verbatim JavaScript.Content ]
)
)
Already answered this on https://github.com/intellifactory/websharper.ui.next/issues/22 but copying my answer here:
If all you want is to create a UI.Next Doc from static html you can do the following:
let htmlElem = JQuery.JQuery.Of("<div>hello</div>").Get(0)
let doc = Doc.Static (htmlElem :?> _)
This is not very nice but should work and I don't think there's a better way to do it at the moment. Or maybe you could use templating but that's not documented yet and I'm not sure it would fit your use case.
Obviously if you want to change the rendered element dynamically you can make it a Var and use Doc.EmbedView together with Doc.Static.

Google Visualization - StringFilter - Searching w. OR

I have stringfilters bind to my table of data.
What i would like to get from the stringfilter is to be able to search like you would do with queries.
There is a colomn with names - a name for each row - for example - "Steve","Monica","Andreas","Michael","Steve","Andreas",...
I want to have both rows with Monica and Steve from the StringFilter.
I would like to be able to search like this
Steve+Monica
or
"Steve"+"Monica"
or
"Steve","Monica"
This is one of my stringfilters:
var stringFilter1 = new google.visualization.ControlWrapper({
controlType: 'StringFilter',
containerId: 'string_filter_div_1',
options: {
filterColumnIndex: 0, matchType : 'any'
}
});
I had a similar problem, and I ended up creating my own function for filtering my rows. I made an example with the function that you describe, but I'm not sure it's the best or the right way, but it works.
One flaw is that you need to type in the names exactly as they are entered, the whole name and with capital letters.
Fiddle, try to add multiple names separated with a "+" (and no spaces).
The function I added looks like this:
function redrawChart(filterString) {
var filterWords = filterString.split("+")
var rows = []
for(i = 0; i < filterWords.length; i++) {
rows = rows.concat(data.getFilteredRows([{value:filterWords[i], column:0}]))
}
return rows
}
And the listener that listens for updates in your string input looks like:
google.visualization.events.addListener(control, 'statechange', function () {
if (control.getState().value == '') {
realChart.setView({'rows': null})
}else{
realChart.setView({'rows': redrawChart(control.getState().value)})
}
realChart.draw();
});
Probably not a complete solution, but maybe some new ideas and directions to your own thoughts.

Resources