I use ditto to show the last entries of a specific parent. For this I am using the following call:
[!Ditto? &parents=`5` &orderBy=`createdon DESC` &display=`3`
&total=`4` &extenders=`summary` &tpl=`tpl_news` &truncLen=`160` &truncOffset=`20` !]
I now want my website to continuously loop through a specific range of parent ids and change the parents parameter every other second.
The result should be a "slideshow" of the content called by the ditto calls.
No special effects, no design (except the one provided by the template).
Is there an easy way to manage this?
Solution:
Got it to work!
I created this resource:
<div id="newsticker_vn">[!Ditto? &parents=`6` &orderBy=`createdon DESC` &display=`1`<br />&total=`4` &extenders=`summary` &tpl=`tpl_news` &truncLen=`160` &truncOffset=`20` !]</div>
<div id="newsticker_hp">[!Ditto? &parents=`5` &orderBy=`createdon DESC` &display=`1`<br />&total=`4` &extenders=`summary` &tpl=`tpl_news` &truncLen=`160` &truncOffset=`20` !]</div>
<div id="newsticker_ks">[!Ditto? &parents=`7` &orderBy=`createdon DESC` &display=`1`<br />&total=`4` &extenders=`summary` &tpl=`tpl_news` &truncLen=`160` &truncOffset=`20` !]</div>
and use this script to get the desired ditto calls and loop them:
<div id="newsticker"></div>
<script type="text/javascript">
function execute() {
$( "#newsticker" ).load("[~348~] #newsticker_vn").delay( 300 ).fadeIn( "slow" ).delay( 6000 ).fadeOut( "slow",execute2 );
}
function execute2() {
$( "#newsticker" ).load("[~348~] #newsticker_ks").delay( 300 ).fadeIn( "slow" ).delay( 6000 ).fadeOut( "slow",execute3 );
}
function execute3() {
$( "#newsticker" ).load("[~348~] #newsticker_hp").delay( 300 ).fadeIn( "slow" ).delay( 6000 ).fadeOut( "slow",execute );
}
execute();
</script>
I tried doing all steps in one function but for some reason it was showing every ditto call three times before rotating, no idea why.
Thank you for your help!
Unless I don't understand what you are trying to do, it's not possible without changing your template(also assuming that your template doesn't support the loop).
From your explanation it seems like you what some sort of ajax functionality. You will need 2 resources setup in the manager.
Your page using whatever template you have
Another page where you get the content for the first page from
First page is your regular page. Second is a page that uses a "blank" template and only has a Ditto call that is randomized. You will have to modify the first template to load the second page using some ajax code.
Related
I am new to SAP UI5 development. Currently the table is using "growing" and "growingThreshhold", then users can click more to see data of next page. Since we have thousands of data in that table, it takes user time to click more and more again to load next page data. we try to implement a function, that user can enter the page number then click a button and go to the specific page.
<Table id="genTable" growing="true" growingThreshold="60" fixedLayout="false" selectionChange="onHandleSelectChange"
backgroundDesign="Solid" updateFinished="onHandleGeneratorQueueUpdateFinished">
Expected UI:
I added a bar then UI display is good.
<Bar design="SubHeader">
<contentMiddle>
<Input type="Number" id="pageNumber" width="50px"></Input>
<Button id="goToButton" text="Go to" type="Emphasized" press="onHandleGoTo"></Button>
</contentMiddle>
</Bar>
For the backend logic, I refer to below articles, but still doesn't work.
https://blogs.sap.com/2016/12/14/sapui5-pagination-in-sap.m-table-on-button-click-using-odata-service/
https://sapyard.com/advance-sapui5-19-pagination-in-table-control-with-top-and-skip-query-options/
I tried to use read, the it can get the data back from odata service, but the data can't be refreshed in the table.
oModel.read("/ViewQueueSet", {
urlParameters: {
"$top": top,
"$skip": count
},
filters: [new Filter("RoleCode", FilterOperator.EQ, "G")],
useBatch: true,
success: function (tdata) { //successful Read in the server
var json = new JSONModel();
json.setData(tdata);
that.getView().setModel(json,"sapmodel");
sap.ui.core.BusyIndicator.hide();
},
error: function () {
sap.ui.core.BusyIndicator.hide();
}
});
}
also tried to call bindItems
//that.getView().setModel(json,"sapmodel");
//oTable.setModel(json); //JSON is preferred data format
//oTable.bindItems("/results",that.oGenQueueTemplate);
that.getView().byId("genTable").setModel(json);
that.getView().byId("genTable").bindItems("/results",that.oGenQueueTemplate);
Another approach I tried is to use bindItems, it call send the request to odata service, but it doesn't add the parameter top and skip parameter.
oTable.bindItems({
path: "/ViewQueueSet",
model: "sapmodel",
filters: [new Filter("RoleCode", FilterOperator.EQ, "G")],
template: this.oGenQueueTemplate,
// urlParameters: {
// "$top": top,
// "$skip": count
// },
parameters: {
"$top": top,
"$skip": count
}
});
Anyone has any idea about how to implement this functionality?
before I go into detail, please consider using other controls and/or ux patterns. imagine having thousands or millions of elements in backend and user equests to scroll to page 9292929 => for a responsive table (sap.m.Table) you would need to load all elements up to that page. Maybe filtering or some completely different approach could be tha right one.
The correct way to do this is by getting the listbinding and ask it to load more elements. how to ask the binding, may depend on the type of binding as well.
oTable = ... // get a reference on table
oItemsBinding = oTable.getBinding("items");
oItemsBinding.getLength() // will give you total number of elements
oItemsBinding.isLengthFinal() // will tell you if the length is final
oItemsBinding.getCurrentContexts() // will give you array of all loaded contexts.
now a few words to length and the length being final. If you have a binding implementation that knows the total number of objects (e.g. json - since it loads all elements to client, or OData, if cont is implemented in backend) then getLength will tell you the total number of objects.
if the backend doesnt have the count feature implemented, the length becomes final once you reach the end of the list (backend gives you less elements than you require - e.g. top=10,skip=90 returns 10 elements => length 100, not final; top=10,skip=100 returns 4 elements => length=104 becomes final)
Now, you can have a look at various binding implementations. But be aware that there is a lot to consider (direction of growing - upwards/downwards), at least you dont need to think about filtering/sorting - as this is part of the binding.
There is a nice (private) feature in sap.m.Table (or in sap.m.ListBase, to be more precise), which is called GrowingEnablement. you can use it like this:
// dont forget if _oGrowingDelagate is not undefined or similar
oTable._oGrowingDelegate.requestNewPage()
this will load one more page => you could start from reading the implementation of this method if you want to load several pages in one go.
you could also do a simple trick:
// assume you have 20 elements per page (default)
// and want to get to 7th page (elements 121 - 140)
// ckecks for 7th page exists and 7th page not yet loaded are omitted
oTable.setGrowingThreshold(70) // half of 140, so following load will load second page => 71 to 140
oTable._oGrowingDelegate.requestNewPage() // this will load the second page 71 - 140
// once loading is finished (take care of asynchronity)
oItemsBinding.attachEventOnce("dataReceived", function(oEvent){
// reset the growing threshold to 20
oTable.setGrowingThreshold(20)
// scroll to first element of 7th page (index 120, since count starts from 0)
oTable.scrollToInex(120)
})
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.
I'm building an application which uses jointjs / rappid and I want to be able to avoid loops from occuring across multiple cells.
Jointjs already has some examples on how to avoid this in a single cell (connecting an "out" port to an "in" port of the same cell) but has nothing on how to detect and prevent loops from occuring further up in the chain.
To help understand, imagine each cell in the paper is a step to be completed. Each step should only ever be run once. If the last step has an "out" port that connects to the "in" port of the first cell, it will just loop forever. This is what I want to avoid.
Any help is greatly appreciated.
I actually found a really easy way to do this for anyone else who wishes to achieve the same thing. Simply include the graphlib dependancy and use the following:
paper.on("link:connect", function(linkView) {
if(graphlib.alg.findCycles(graph.toGraphLib()).length > 0) {
linkView.model.remove();
// show some error message here
}
});
This line:
graphlib.alg.findCycles(graph.toGraphLib())
Returns an array that contains any loops, so by checking the length we can determine whether or not the paper contains any loops and if so, remove the link that the user is trying to create.
Note: This isn't completely full-proof because if the paper already contains a loop (before the user adds a link) then simply removing the link that the user is creating won't remove any loop that exists. For me this is fine because all of my papers will be created from scratch so as long as this logic is always in place, no loops can ever be created.
Solution through graphlib
Based on Adam's graphlib solution, instead of findCycles to test for loops, the graphlib docs suggests to use the isAcyclic function, which:
returns true if the graph has no cycles and returns false if it does. This algorithm returns as soon as it detects the first cycle.
Therefore this condition:
if(graphlib.alg.findCycles(graph.toGraphLib()).length > 0)
Can be shortened to:
if(!graphlib.alg.isAcyclic(graph))
JointJS functions solution
Look up the arrays of ancestors and successors of a newly connected element and intersect them:
// invoke inside an event which tests if a specific `connectedElement` is part of a loop
function isElementPartOfLoop (graph, connectedElement) {
var elemSuccessors = graph.getSuccessors(connectedElement, {deep: true});
var elemAncestors = connectedElement.getAncestors();
// *** OR *** graph.getPredecessors(connectedElement, {deep: true});
var commonElements = _.intersection(elemSuccessors, elemAncestors);
// if an element is repeated (non-empty intersection), then it's part of a loop
return !_.isEmpty(commonElements);
}
I haven't tested this, but the theory behind the test you are trying to accomplish should be similar.
This solution is not as efficient as using directly the graphlib functions.
Prevention
One way you could prevent the link from being added to the graph is by dealing with it in an event:
graph.on('add', _.bind(addCellOps, graph));
function addCellOps (cell, collection, opt) {
if (cell.isLink()){
// test link's target element: if it is part of a loop, remove the link
var linkTarget = cell.getTargetElement();
// `this` is the graph
if(target && isElementPartOfLoop(this, linkTarget)){
cell.remove();
}
}
// other operations ....
}
I am using Node.js with the Jade template engine to create a test web page, to confirm that some features will work in the real project.
Depending on the first digit of the client's IP address, I want to render a Jade page with a table on it. The number of rows in the table is equal to that first digit from earlier. For example, if the client's address is 192.198.94.227, the table will have 1 row. If it is 92.61.131.242, the table will have 9 rows.
I know this sounds very silly, but I am only using this to test if I can add varying number of table rows for my real website.
Is there any way in Jade or Node.js code to represent this?
Or if I cannot do this, is there any work-around?
When you render a page using Jade template, you can provide a set of variables to the template.
This is exemplary express method handling '/example' request.
First argument of req.render specifies template name, the second argument (an object) specifies template parameters. In this case there are two parameters: ip (a customers IP) and ipFirstDigit - first digit of the customer IP address.
function remoteAddr(req) {
if (req.ip) {
return req.ip;
}
var socket = req.socket;
if (socket.socket) {
return socket.socket.remoteAddress;
}
return socket.remoteAddress;
};
app.get('/example', function(req, res){
var ip = remoteAddr(req);
res.render('index', { ip: ip, ipFirstDigit:ip.charAt(0) });
});
Jade template can looks as follows:
p Customer IP: #{ip}
p Customer IP first digit: #{ipFirstDigit}
table
- for (var i = 0; i < ipFirstDigit; i++)
tr
td #{i+1}
First two lines display customer IP and the first digit of the customer IP. Then a table is rendered depending on the first digit of the customer IP value.
Output for IP:92.61.131.242 is as follows:
<p>Customer IP: 92.61.131.242</p>
<p>Customer IP first digit: 9</p>
<table>
<tbody>
<tr><td>1</td></tr>
<tr><td>2</td></tr>
<tr><td>3</td></tr>
<tr><td>4</td></tr>
<tr><td>5</td></tr>
<tr><td>6</td></tr>
<tr><td>7</td></tr>
<tr><td>8</td></tr>
<tr><td>9</td></tr>
</tbody>
</table>
I hope that will help with your prototype.
Actually, Jade is just a template engine. The right solution is to use JavaScript to modify DOM (Document Object Model). Think about Jade is just the structure for displaying your data. But before doing that, you need to manipulate those data first. Use Node.js to collect some necessary information, then use JavaScript (jQuery, AngularJS, etc.) to manipulate DOM in HTML template (with Jade).
I'm using the search portlet in certain areas of my website, but I'd like to restrict the results to only search for a specific content type: for example only search the news items, or only show Faculty Staff Directory profiles.
I know you can do this after you get to the ##search form through that "filter" list, but is there a way to start with the filter on, so that the "Live Search" results only show the relevant results (i.e. only news items or only profiles).
I suspect you know it already, but just to be sure: You can globally define which types should be allowed to show up in searchresults in the navigations-settings of the controlpanel, and then export and include the relevant parts to your product's GS-profile-propertiestool.xml.
However, if you would like to have some types excluded only in certain sections, you can customize Products.CMFPlone/skins/plone_scripts/livesearch_reply, which already filters the types, to only show "friendly_types" around line 38 (version 4.3.1) and add a condition like this:
Edit:
I removed the solution to check for the absolute_url of the context, because the context is actually the livesearch_reply in this case, not the current section-location. Instead the statement checks now, if the referer is our section:
REQUEST = context.REQUEST
current_location = REQUEST['HTTP_REFERER']
location_to_filter = '/fullpath/relative/to/siteroot/sectionId'
url_to_filter = str(portal_url) + location_to_filter
types_to_filter = ['Event', 'News Item']
if current_location.find(url_to_filter) != -1 or current_location.endswith(url_to_filter):
friendly_types = types_to_filter
else:
friendly_types = ploneUtils.getUserFriendlyTypes()
Yet, this leaves the case open, if the user hits the Return- or Enter-key or the 'Advanced search...'-link, landing on a different result-page than the liveresults have.
Update:
An opportunity to apply the filtering to the ##search-template can be to register a Javascript with the following content:
(function($) {
$(document).ready(function() {
// Let's see, if we are coming from our special section:
if (document.referrer.indexOf('/fullpath/relative/to/siteroot/sectionId') != -1) {
// Yes, we have the button to toggle portal_type-filter:
if ($('#pt_toggle').length>0) {
// If it's checked we uncheck it:
if ($('#pt_toggle').is(':checked')) {
$('#pt_toggle').click();
}
// If for any reason it's not checked, we check and uncheck it,
// which results in NO types to filter, for now:
else {
$('#pt_toggle').click();
$('#pt_toggle').click();
}
// Then we check types we want to filter:
$("input[value='Event']").click();
$("input[value='News Item']").click();
}
}
})
})(jQuery);
Also, the different user-actions result in different, inconsistent behaviours:
Livesearch accepts terms which are not sharp, whereas the ##search-view only accepts sharp terms or requires the user to know, that you can append an asterix for unsharp results.
When hitting the Enter/Return-key in the livesearch-input, the searchterm will be transmitted to the landing-page's (##search) input-element, whilst when clicking on 'Advanced search...' the searchterm gets lost.
Update:
To overcome the sharp results, you can add this to the JS right after the if-statement:
// Get search-term and add an asterix for blurry results:
var searchterm = decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI('SearchableText').replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1")) + '*';
// Insert new searchterm in input-text-field:
$('input[name=SearchableText]').val(searchterm);
Update2:
In this related quest, Eric Brehault provides a better solution for passing the asterix during submit: Customize Plone search
Of course you can also customize the target of advanced-search-link in livesearch_reply, respectively in the JS for ##search, yet this link is rather superfluous UI-wise, imho.
Also, if you're still with Archetypes and have more use-cases for pre-filtered searchresults depending on the context, I can recommend to have a look at collective.formcriteria, which allows to define search-criteria via the UI. I love it for it's generic and straightforward plone-ish approach: catalogued indizi and collections. In contradiction to eea.facetednavigation it doesn't break accessibility and can be enhanced progressively with some live-search-js-magic with a little bit of effort, too. Kudos to Ross Patterson here! Simply turn a collection (old-style) into a searchform by changing it's view and it can be displayed as a collection-portlet, as well. And you can decide which criteria the user should be able to change or not (f.e. you hide the type-filter and offer a textsearch-input).
Watch how the query string changes when you use the filter mechanism on the ##search page. You're simply adding/subtracting catalog query criteria.
You may any of those queries in hidden fields in a search form. For example:
<form ...>
....
<input type="hidden" name="portal_type" value="Document" />
</form>
The form on the query string when you use filter is complicated a bit by its record mechanism, which allows for some min/max queries. Simple filters are much easier.