Dynamic selectone in alfresco share - drop-down-menu

For a form in Alfresco share, I want a dropdown box that is filled with custom options depending on the value of a field earlier up in the form.
My form would have at least two fields. The first one a textbox, where a unique code must be entered. When that is done, the second one, a select box, must load it's options using the entered code.
The data backing this requirement is stored in a Data list. I have also made it available through a webscript (along the lines of /getOptions/{uniqueCode, returning a JSON array of the valid options.
Now, I am a bit stuck on how to build the part of the form that will watch for status changes on the code textfield, and reload the dropdown box. I can think of some javascript, but I don't even know where to start changing/adding files.
I've looked through the FDK, where I found the selectone ftl. Unfortunately, this supports only fixed options.
My implementation based on my chosen answer
This is very similar to what I was already doing, I had hoped to be able to do this on the server side, without including the extra round-trip. So far, this is the best I have though.
share-config-custom.xml
I define the form here, and point the property I want to be my selectone to my own custom field template. I pass a parameter ds to it, dataSource, which holds the path to my webscript.
<config evaluator="node-type" condition="my:contentType">
<forms>
<form>
<field-visibility>
<show id="my:code" />
<show id="my:description" />
</field-visibility>
<appearance>
<set id="general" appearance="bordered-panel" label="General" />
<field id="my:description" set="general">
<control template="/org/alfresco/components/form/controls/customSelectone.ftl">
<control-param name="ds">/alfresco/service/mark/cache/options</control-param>
</control>
</field>
</appearance>
</form>
</forms>
customSelectone.ftl
My custom ftl has three major steps. First, it receives the ftl parameter I passed from share config custom and assigns it to a local variable. Then it places a html <select>box as a field, and finally, it executes a call to my webscript for the possible options.
Parameter
<#if field.control.params.ds?exists><#assign ds=field.control.params.ds><#else><#assign ds=''></#if>
html
<style type="text/css">
#${fieldHtmlId}-AutoComplete {
width:${width}; /* set width here or else widget will expand to fit its container */
padding-bottom:2em;
}
</style>
<div class="form-field">
<#-- view form -->
<#if form.mode == "view">
<div class="viewmode-field">
<#if field.mandatory && !(field.value?is_number) && field.value == "">
<span class="incomplete-warning"><img src="${url.context}/components/form/images/warning-16.png" title="${msg("form.field.incomplete")}" /><span>
</#if>
<span class="viewmode-label">${field.label?html}:</span>
<span class="viewmode-value">${field.value?html}</span>
</div>
<#else>
<#-- alternative: if form.mode == "edit" -->
<#-- Create/edit form -->
<label for="${fieldHtmlId}">${field.label?html}:<#if field.mandatory><span class="mandatory-indicator">${msg("form.required.fields.marker")}</span></#if></label>
<div id="${fieldHtmlId}-AutoComplete">
<#-- Label to hold error messages from the javascript -->
<p style="color:red" id="${fieldHtmlId}-scriptError"></p>
<select id="${fieldHtmlId}" name="${field.name}"
<#if field.control.params.styleClass?exists>class="${field.control.params.styleClass}"</#if>
<#if field.description?exists>title="${field.description}"</#if>
<#if field.control.params.size?exists>size="${field.control.params.size}"</#if>
<#if field.disabled>disabled="true"</#if> >
<#-- Add the field's current value if it has one as an option -->
<option>${field.value}</option>
</select>
<div id="${fieldHtmlId}-Container"></div>
</div>
</div>
Javascript
<script type="text/javascript">//<![CDATA[
(function()
{
<#-- This references the code field from the form model. For this, the -->
<#-- share config must be set to show the field for this form. -->
<#if form.fields.prop_my_code??>
var code = "${form.fields.prop_my_code.value}";
<#else>
var code = 0;
</#if>
// get code
if(code === null || code === "") {
document.getElementById('${fieldHtmlId}-scriptError').innerHTML = 'No description available.';
return;
}
// Create webscript connection using yui connection manager
// Note that a much more elegant way to call webscripts using Alfresco.util is
// available in the answers here.
var AjaxConnectionManager = {
handleSuccess:function(o) {
console.log('response: '+o.responseText);
this.processResult(o);
},
handleFailure:function(o) {
var selectBox = document.getElementById('${fieldHtmlId}');
var i;
document.getElementById('${fieldHtmlId}-scriptError').innerHTML = 'Descriptions not available.';
},
startRequest:function() {
console.log('webscript call to ${ds} with params code='+code);
YAHOO.util.Connect.asyncRequest('GET', "${ds}?typecode="+code, callback, null);
},
processResult:function(o) {
var selectBox = document.getElementById('${fieldHtmlId}');
var jso = JSON.parse(o.responseText);
var types = jso.types;
console.log('adding '+types.length+' types to selectbox '+selectBox);
var i;
for(i=0;i<types.length;i++) {
// If the current field's value is equal to this value, don't add it.
if(types[i] === null || types[i] === '${field.value}') {
continue;
}
selectBox.add(new Option(types[i], types[i]));
}
}
}
// Define callback methods
var callback = {
success:AjaxConnectionManager.handleSuccess,
failure:AjaxConnectionManager.handleFailure,
scope: AjaxConnectionManager
};
// Call webscript
AjaxConnectionManager.startRequest();
})();
//]]></script>
<#-- This closes the form.mode != "create" condition, so the js is only executed when in edit/create mode. -->
</#if>

I had a similar task before.
First you need to define a custom template in your configuration xml
<config evaluator="node-type" condition="my:type">
<forms>
<form>
<field-visibility>
<show id="cm:name" />
<show id="my:options" />
<show id="cm:created" />
<show id="cm:creator" />
<show id="cm:modified" />
<show id="cm:modifier" />
</field-visibility>
<appearance>
<field id="my:options">
<control template="/org/alfresco/components/form/controls/custom/custom-options.ftl" />
</field>
</appearance>
</form>
</forms>
</config>
What happens here is that the form engine will look for custom-options.ftl to render my:options for type my:type.
custom-options.ftl will contain the html needed to display your data and of course the call to javascript class that will load your list from your webscript.
So it looks like this
<#assign controlId = fieldHtmlId + "-cntrl">
<script type="text/javascript">//<![CDATA[
// Here you could call your webscript and load your list
</script>
<div id="${controlId}" class="form-field">
<label for="${fieldHtmlId}">${msg("form.control.my-options.label")}:<#if field.mandatory><span class="mandatory-indicator">${msg("form.required.fields.marker")}</span></#if></label>
<select id="${fieldHtmlId}" name="${field.name}" tabindex="0"
<#if field.description??>title="${field.description}"</#if>
<#if field.control.params.size??>size="${field.control.params.size}"</#if>
<#if field.control.params.styleClass??>class="${field.control.params.styleClass}"</#if>
<#if field.control.params.style??>style="${field.control.params.style}"</#if>>
</select>
<#formLib.renderFieldHelp field=field />
</div>

You can call webscript like this:
<script type="text/javascript">//<![CDATA[
var updateOptions = function(res){
var result = eval('(' + res.serverResponse.responseText + ')');
if(result.Options.length > 0 ) { // Options - returned JSON object
// do something with JSON data
}
}
Alfresco.util.Ajax.jsonGet({
url : Alfresco.constants.PROXY_URI + "/getOptions/{uniqueCode}"+ (new Date().getTime()),
successCallback : {
fn : updateOptions,
scope : this
},
failureCallback : {
fn : function() {},
scope : this
}
});
//]]></script>

Related

Grails, sorting and filtering a grid at the same time using ajax

I have a grid(html-table) with a lot of columns and want to combine filtering and sorting on this table.
At the moment I only use filtering on one column but sorting on several columns.
I want to do this with ajax.
I read an article [http://www.craigburke.com/2011/01/23/grails-ajax-list-with-paging-sorting-and-filtering.html]
and tried to adapt it to my version of grails-3.2.6.
This has been very hard to solve and now I'm totally stuck.
If I add something in the filter nothing happens but when I click on a column, the filtering takes place and also sorting, clicking a second time, ajax is not called and the filter is overwritten with the default value. I have managed to implement it on a test project which act the same.
It is much code and maybe there is a way to include the whole project in this question in some way?
I'll try to show most of the important part here if it could help.
The index.gsp:
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
<g:set var="entityName" value="${message(code: 'person.label', default: 'Person')}" />
<title><g:message code="default.list.label" args="[entityName]" /></title>
<script type="text/javascript">
$(document).ready(function() {
setupGridAjax();
setupFilterAjax();
});
</script>
<script type="text/javascript">
function setupGridAjax() {
$('#gridPersons').find('.paginateButtons a, th.sortable a').on('click', function(event) {
event.preventDefault();
var url = $(this).attr('href');
var grid = $(this).parents("table.ajax");
$(grid).html($("#spinner").html());
$.ajax({
type: 'GET',
url: url,
data: [tag],
success: function(data) {
$(grid).fadeOut('fast', function() {$(this).html(data).fadeIn('slow');});
}
})
});
}
</script>
<script type="text/javascript">
// Turn any input changes or form submission within a filter div into an ajax call
function setupFilterAjax(){
alert('FILTER--Anropat');
$('div.filters select:input').on('change',function(event) {
var filterBox = $(this).parents("div.filters");
filterGrid(filterBox);
});
$("div.filters form").submit(function() {
var filterBox = $(this).parents("div.filters");
alert('FILTERBOX - '+filterBox);
filterGrid(filterBox);
return false;
});
}
// Reload grid based on selections from the filter
function filterGrid(filterBox) {
alert('FILTER-change detected');
var grid = $(filterBox).next("div.gridPersons");
$(grid).html($("#spinner").html());
var form = $(filterBox).find("form");
var url = $(form).attr("action");
var data = $(form).serialize();
alert('FILTERGRID - '+url);
$.ajax({
type: 'POST',
url: '${g.createLink( controller:'person', action:'index' )}',
data: [tag],
success: function(data) {
$(grid).fadeOut('fast', function() {$(this).html(data).fadeIn('slow');});
}
});
}
</script>
</head>
<body>
<g:message code="default.link.skip.label" default="Skip to content…"/>
<div class="nav" role="navigation">
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
</ul>
</div>
<div id="list-person" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<div class="filters">
<g:form action="register">
<div id="selectMill">
Select tags:
<g:select class="selected" name="tag" from="${tagList}" value="${filters?.tag}" noSelection = "${['':'All']}" optionValue="" optionKey="" />
</div>
<div id="gridPersons">
<g:render template="Grid_Persons" model="personList" />
</div>
<div class="pagination">
<g:paginate total="${personCount ?: 0}" />
</div>
<fieldset class="buttons">
<input class="save" type="submit" value="${message(code: 'offer.create.from.buffer.label', default: 'Create')}" />
</fieldset>
</g:form>
</div>
</div>
</body>
The template: _Grid_Persons.gsp
<table class="ajax">
<thead>
<tr>
<g:sortableColumn property='reg' title='Register' />
<g:sortableColumn property="id" title='Id' params="${filters}"/>
<g:sortableColumn property='name' title='Name' params="${filters}"/>
<g:sortableColumn property='tag' title='Tag' params="${filters}"/>
<g:sortableColumn property='registered' title='Registered' params="${filters}"/>
</thead>
<tbody>
<g:each in="${personList}" status="i" var="ps">
<tr class="${ (i % 2) == 0 ? 'even': 'odd'}">
<td><g:checkBox name="ckb" value="${ps.id}" checked="false" /></td>
<td><g:link action="edit" id="${ps.id}">${ps.id}</g:link></td>
<td>${ps.name}</td>
<td>${ps.tag}</td>
<td>${ps.registered}</td>
<td>
</g:each>
</tbody>
The index-part of the controller:
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
def tagList = Person.withCriteria {
projections {
distinct("tag")
}
}
def List<Person> personList = getPersonList()
// Paging def prodBuffer = getPaginatedList(prodBuffer, max, params.offset?.toInteger())
def filters = [tag: params.tag, sort: params.sort, order: params.order]
def model = [personList: personList, filters:filters, tagList:tagList]
if (request.xhr) {
println("AJAX-Request!!!")
render(template:"Grid_Persons", model:model)
prodBuffer, offerDetails:offerDetails, filters:filters])
} else {
offerDetails: offerDetails, millList: millList, selectedMill:false, prodBufferCount: ProdBuffer.count()]
[personList:personList,tagList:tagList]
}
Person.count(), tagList:tagList]
}
def List<Person> getPersonList() {
println("getPersonList tag: "+params.tag)
def tag = params.tag
def c = Person.createCriteria()
def tempList = c.list {
if (tag) eq("tag", tag)
if (params.sort){
order(params.sort, params.order)
}
}
return tempList
}
From debugging:
The page is loaded:
getPersonList tag: null
Selected "Grails" in the filter:
getPersonList tag: Grails
AJAX-Request!!!
Klicked on the "Name"-column header
getPersonList tag: Grails
AJAX-Request!!!
-- Now the list is sorted(ascending) and filtered
Klicked on the column again:
getPersonList tag: Grails
-- Now the list is resorted(descending) and the filtering still ok but the SELECT TAGS now view "All"
Klicked the column for the third time:
getPersonList tag:
AJAX-Request!!!
-- Now the list show all lines and resorted(ascending)
Now solved by using the recommended plugin - datatables.
In case you are in a hurry you can inject https://datatables.net/ that adds the utilities you require among others

Updating .NET Charts using ajax.beginform

I'm using .NET Charts to create some dynamic charts from a table in a database, and each chart is a separate view, with a corresponding action in the controller.
Then they are displayed in a main view as images:
<img src="~/Controller/Chart1" class="centered" />
Now I want to be able to filter the charts by date, and I've added the parameters to the actions and a couple datepickers for a start date and an end date.
I'm trying to refresh the charts using Ajax, but I'm having trouble. Without Ajax, the filter works, but redirects to a page containing just the updated chart.
With Ajax, nothing happens, or rather, if I set the UpdateTargetId to a div it gets filled with text, like byte code or something.
This is what I'm using:
<div>
using (Ajax.BeginForm("Chart1", "controller",
new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "Chart1"
}))
{
<p>
<input type="text" name="begindate" class="datefield" placeholder="Begin Date" />
<input type="text" name="enddate" class="datefield" placeholder="End Date" />
<input type="submit" value="Filter" />
</p>
<img src="~/controller/Chart1" class="centered" />
</div>
How can I fix this problem?
You need to pass the dates to your action method and let the action method uses these dates to generate a filtered data set which will be used to generate the chart.
You can simply use jQuery Ajax to do this.
<div>
<input type="text" name="begindate" class="datefield" placehold="Begin Date" />
<input type="text" name="enddate" class="datefield" placeholder="End Date" />
<input type="submit" id="btnFilter" value="Filter" />
<img src="~/controller/Chart1" id="chartImg" class="centered" />
</div>
Now when user clicks the form, pass the dates
$(function(){
$("#btnFilter").click(function(e){
e.preventDefault();
var d1 = $("input[name='begindate']").val();
var d2 = $("input[name='enddate']").val();
var url = "#Url.Action("Chart1", "YourControllerName")";
url += "?beginDate=" + d1 + "&endDate=" + d2;
$("#chartImg").attr("src", url);
});
});
Now of course your action method should accept the dates and return the data as needed:
public ActionResult Chart1(DateTime beginDate,DateTime endDate)
{
// Return the chart image
}
I am using the Url.Action action method to generate the correct path to the Chart1 action method. This code will work if your JavaScript code is inside a Razor view. If your code is inside an external js file, Use the approach mentioned in this post

KendoMobile ui template not rendering css How to make the template render with kendo stylng in view?

Basically the template wont render to a ScrollView using kendo.render(template, response) but WILL work with content = template(response) - BUT this has no styling in view -- see comment below
How to make the template render with kendo stylign in view?
BTW response from api call is JSON:
{"event_id":"5","stamp":"2013-01-24 06:00:00","type":"Event Type","loc":"Location","status":"1"}
<!-- eventDetail view -------------------------------------------------------------------------------------------------->
<div data-role="view" id="view-eventDetail" data-show="getEventDetailData" data-title="eventDetail">
<header data-role="header">
<div data-role="navbar">
<span data-role="view-title"></span>
<a data-align="right" data-role="button" class="nav-button" href="#view-myEvents">Back</a>
</div>
</header>
<div id="eventDetail" data-role="page"></div>
</div>
<script id="eventDetail-template" type="text/x-kendo-template">
--><form id="addEventForm"><p>
<input name="event_type" id="event_type" data-min="true" type="text" value="#= type #" />
</p>
<p>
<input name="event_loc" id="event_loc" data-min="true" type="text" value="#= loc #" />
</p>
<p>
<input name="event_date_time" id="event_date_time" data-min="true" type="datetime" value="#= stamp#" />
</p>
<p>
Share this
<input data-role="switch" id="event_share" data-min="true" checked="checked" value="1"/></p>
<p>
<input type="button" id="eventCancelButton" style="width:30%" data-role="button" data-min="true" value="Cancel" />
<input type="submit" id="eventDoneButton" style="width:30%" data-role="button" data-min="true" value="Done" />
</p></form><!--
</script>
<script>
//eventDetail engine
function getEventDetailData(e) {
$.ajax({
url: 'http://localhost/mpt/website/api/event_details.php?',
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: { userID: 2, eventID: e.view.params.id },
success: function(response) {
console.log(response);
var template = kendo.template($("#eventDetail-template").html()),
content = template(response);//works but no kendo css
//content = kendo.render(template, response);not working
$("#eventDetail")
.kendoMobileScrollView()
.data("kendoMobileScrollView")
.content("<!--" + content + "-->");
}
});
}</script>
The widget classes (like km-button) are not added until the widget is initialized.
The template() and render() functions just return the template as a string with the data replaced (replaces #=foo# with the value of the foo property) but does not init all the widgets. In fact, it coldn't initialize the widgets if it wanted to singe it just returns a text string, not DOM elements. The initialization of the widgets is usually done by the parent widget that is using the template.
render() is not working in your case because its 2nd argument is supposed to be an array. All it does is call the given template function once per item in the array and concatenate the results. If you instead did:
var content = kendo.render(template, [response]); // wrap response in an array
it would return the same text string as template(response). It just provides a way to apply the same template to many items at once.
Normally when you create a widget, in your case calling .kendoMobileScrollView() you would expect it to turn any HTML contents of that element into widgets too, but it looks like the ScrollView widget doesn't do this. I think its intent may have been to just display pages of static content, not other widgets.
There is a Kendo method that isn't listed in the docs, kendo.mobile.init(contents); that you might be able to use to turn your template string into widgets. When I tried it in a jsFiddle it threw some error for me, but you could try something like:
var content = template(response); // apply response to template
var contentElements = $(content); // turn the string into DOM elements
kendo.mobile.init(contentElements); // turn elements into widgets (this throws error for me)
$("#eventDetail").html(contentElements); // add contents to the desired element
$("#eventDetail").kendoMobileScrollView(); // create the scroll view
Also, what is with the end and begin comment bits hanging off the ends of the template? I don't see why those are needed. Might be better to remove them.
The ScrollView widget is supposed to take a series of <div> elements as its children. It then pages between them as you swipe left/right across the control. I don't see you adding a series of <div>s anywhere.

CKEditor iframe-based plugin and how to return radio value

I have created an iframe-based plugin for CKEDitor using CKEDITOR.dialog.addIframe and want the user to select a radio value, which will be returned to the editor. I am using the code below to try and return the value.
<form name="form1">
<label><input type="radio" name="field_name" value="[value one]" id="field_name_0" onclick="return get_radio_value()" />value one</label><br />
<label><input type="radio" name="field_name" value="[value two]" id="field_name_1" onclick="return get_radio_value()" />value two</label><br />
<label><input type="radio" name="field_name" value="[value three]" id="field_name_2" onclick="return get_radio_value()" />value three</label><br />
</form>
<script language="javascript">
function get_radio_value()
{
for (var i=0; i < document.form1.field_name.length; i++)
{
if (document.form1.field_name[i].checked)
{
var rad_val = document.form1.field_name[i].value;
//alert(rad_val); //this works using onclick
}
}
}
var CKEDITOR = window.parent.CKEDITOR;
var okListener = function(ev) {
this._.editor.insertHtml('<div class="custom_form">'+rad_val+'</div>');
CKEDITOR.dialog.getCurrent().removeListener("ok", okListener);
};
CKEDITOR.dialog.getCurrent().on("ok", okListener);
</script>
I also tried the simple:
var form_value = document.form1.field_name.value;
this._.editor.insertHtml('<div class="custom_form">'+form_value+'</div>');
but this returned "undefined"
Any help or ideas would be appreciated?
Note: Form field values are dynamically created via PHP and fed from a MySQL database.

Can I load the max value of the range attribute in CFINPUT using and AJAX call?

I have a CFINPUT tag in a CFFORM. I want to set the range dynamically without posting the page. I have several AJAX calls throughout the page to dynamically load form fields on the fly:
<cfselect id="this" name="this" bind="cfc:Data.getThis()" bindonload="true" />
<cfselect id="that" name="that" bind="cfc:Data.getThat({p1})" />
<cfselect id="theOther" name="theOther" bind="cfc:Data.getTheOther({p1}, {p2})" />
<cfdiv
id="maxQty"
bind="cfc:Data.getMaxQty({itemId})" />
<cfinput
type="text"
id="qty"
name="qty" />
<cfdiv
id="itemId"
bind="cfc:Data.getItemId({this}, {that}, {theOther})" />
In the above CFFORM, I basically want to set the minValue of the range to "1" and the maxValue of the range to the value of cfc:Data.getMaxQty({itemId}).
Is this possible? How can I do it?
The quick answer is "No". But there is a very easy workaround. Simply load the value that you want to be the maximum into a CFDIV or a CFINPUT hidden field using binding, then access that value in a JavaScript function that validates the min/max values when you submit the form:
<script type="text/javascript">
<!--
function validateForm() {
var maxQty = document.getElementById("maxQty").innerHTML;
if (document.myForm.add_item_1.value < 1 || document.myForm.add_item_1.value > maxQty) {
alert("Quantity must be an integer value between 1 and " + maxQty);
return false;
}
return true;
}
//-->
</script>
<cfform name="myForm" method="post" action="myFormAction.csm" onsubmit="return validateForm();">
<cfdiv
id="maxQty"
bind="cfc:Data.getMaxQty({itemId})" />

Resources