001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.document.web.renderers;
017    
018    import java.io.IOException;
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import javax.servlet.jsp.JspException;
025    import javax.servlet.jsp.JspWriter;
026    import javax.servlet.jsp.PageContext;
027    import javax.servlet.jsp.tagext.Tag;
028    
029    import org.apache.commons.lang.StringUtils;
030    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
031    import org.kuali.kfs.sys.context.SpringContext;
032    import org.kuali.kfs.sys.document.AccountingDocument;
033    import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition;
034    import org.kuali.kfs.sys.document.datadictionary.AccountingLineViewActionDefinition;
035    import org.kuali.kfs.sys.document.web.AccountingLineViewAction;
036    import org.kuali.rice.kns.authorization.AuthorizationConstants;
037    import org.kuali.rice.kns.service.KNSServiceLocator;
038    import org.kuali.rice.kns.service.KualiConfigurationService;
039    import org.kuali.rice.kns.web.taglib.html.KNSFileTag;
040    import org.kuali.rice.kns.web.taglib.html.KNSImageTag;
041    
042    /**
043     * Renders the standard group header/import line
044     */
045    public class GroupTitleLineRenderer implements Renderer, CellCountCurious {
046        private int titleCellSpan = 4;
047        private int cellCount = 1;
048        private AccountingLineGroupDefinition accountingLineGroupDefinition;
049        private AccountingDocument accountingDocument;
050        private String lineCollectionProperty;
051        private KNSFileTag scriptFileTag = new KNSFileTag();
052        private KNSFileTag noscriptFileTag = new KNSFileTag();
053        private KNSImageTag uploadButtonTag = new KNSImageTag();
054        private KNSImageTag cancelButtonTag = new KNSImageTag();
055        private boolean shouldUpload = true;
056        private boolean canEdit = false;
057    
058        private boolean groupActionsRendered = false;
059    
060        /**
061         * Constructs a ImportLineRenderer, setting defaults on the tags that will always exist
062         */
063        public GroupTitleLineRenderer() {
064            scriptFileTag.setSize("30");
065            noscriptFileTag.setSize("30");
066            noscriptFileTag.setStyle("font:10px;height:16px;");
067            uploadButtonTag.setSrc(KNSServiceLocator.getKualiConfigurationService().getPropertyString("externalizable.images.url") + "tinybutton-add1.gif");
068            uploadButtonTag.setStyleClass("tinybutton");
069            cancelButtonTag.setProperty("methodToCall.cancel");
070            cancelButtonTag.setSrc(KNSServiceLocator.getKualiConfigurationService().getPropertyString("externalizable.images.url") + "tinybutton-cancelimport.gif");
071            cancelButtonTag.setStyleClass("tinybutton");
072        }
073    
074        /**
075         * @see org.kuali.kfs.sys.document.web.renderers.Renderer#clear()
076         */
077        public void clear() {
078            cellCount = 1;
079            accountingLineGroupDefinition = null;
080            titleCellSpan = 4;
081            lineCollectionProperty = null;
082            accountingDocument = null;
083            shouldUpload = true;
084            canEdit = false;
085    
086            // clean script file tag
087            scriptFileTag.setPageContext(null);
088            scriptFileTag.setParent(null);
089            scriptFileTag.setProperty(null);
090    
091            // clean noscript file tag
092            noscriptFileTag.setPageContext(null);
093            noscriptFileTag.setParent(null);
094            noscriptFileTag.setProperty(null);
095    
096            // clean upload button tag
097            uploadButtonTag.setPageContext(null);
098            uploadButtonTag.setParent(null);
099            uploadButtonTag.setProperty(null);
100            uploadButtonTag.setAlt(null);
101            uploadButtonTag.setTitle(null);
102    
103            // clean cancel import tag
104            cancelButtonTag.setPageContext(null);
105            cancelButtonTag.setParent(null);
106            cancelButtonTag.setAlt(null);
107            cancelButtonTag.setTitle(null);
108            cancelButtonTag.setOnclick(null);
109        }
110    
111        /**
112         * @see org.kuali.kfs.sys.document.web.renderers.Renderer#render(javax.servlet.jsp.PageContext, javax.servlet.jsp.tagext.Tag,
113         *      org.kuali.core.bo.BusinessObject)
114         */
115        public void render(PageContext pageContext, Tag parentTag) throws JspException {
116            try {
117                pageContext.getOut().write(buildRowBeginning());
118                
119                pageContext.getOut().write(buildTitleCell());
120                this.renderGroupLevelActions(pageContext, parentTag);
121                
122                pageContext.getOut().write(buildRowEnding());
123            }
124            catch (IOException ioe) {
125                throw new JspException("Difficulty in rendering import/group header line", ioe);
126            }
127        }
128    
129        /**
130         * Builds a tag for the row beginning
131         * 
132         * @returns the String with the HTML for the row opening
133         */
134        protected String buildRowBeginning() {
135            return "<tr>";
136        }
137    
138        /**
139         * Builds the tag for the row beginning
140         * 
141         * @returns the String with the HTML for the row beginning
142         */
143        protected String buildRowEnding() {
144            return "</tr>";
145        }
146        
147        protected void renderGroupLevelActions(PageContext pageContext, Tag parentTag) throws JspException {
148            JspWriter out = pageContext.getOut();
149            
150            try {
151                out.write(this.buildGroupActionsBeginning());
152                
153                this.renderGroupActions(pageContext, parentTag);
154                
155                this.renderUploadCell(pageContext, parentTag);
156                
157                out.write(this.buildGroupActionsColumnEnding());
158            }
159            catch (IOException ioe) {
160                throw new JspException("Difficulty rendering group level actions", ioe);
161            }
162        }
163    
164        /**
165         * Builds a tag for the row beginning
166         * 
167         * @returns the String with the HTML for the row opening
168         */
169        protected String buildGroupActionsBeginning() { 
170            if (this.canUpload() || this.isGroupActionsRendered()) { 
171                StringBuilder groupActionsBeginning = new StringBuilder();
172                final int width = cellCount - titleCellSpan;
173                
174                groupActionsBeginning.append("<td ");
175                groupActionsBeginning.append("colspan=\"");
176                groupActionsBeginning.append(Integer.toString(width));
177                groupActionsBeginning.append("\" ");
178    
179                groupActionsBeginning.append("class=\"tab-subhead-import\" ");
180                groupActionsBeginning.append("align=\"right\" ");
181                groupActionsBeginning.append("nowrap=\"nowrap\" ");
182                groupActionsBeginning.append("style=\"border-right: none;\"");
183                groupActionsBeginning.append(">");
184                
185                return groupActionsBeginning.toString();
186            }
187    
188            return StringUtils.EMPTY;
189        }
190    
191        /**
192         * Builds the tag for the row beginning
193         * 
194         * @returns the String with the HTML for the row beginning
195         */
196        protected String buildGroupActionsColumnEnding() {
197            return this.canUpload() || this.isGroupActionsRendered() ? "</td>" : StringUtils.EMPTY;
198        }
199    
200        /**
201         * Builds the tags for the title cell of the import line
202         * 
203         * @return the String with the HTML for the title cell
204         */
205        protected String buildTitleCell() {
206            StringBuilder titleCell = new StringBuilder();
207            int colSpan = (this.canUpload() || this.isGroupActionsRendered()) ? titleCellSpan : cellCount;
208    
209            titleCell.append("<td ");
210    
211            titleCell.append("colspan=\"");
212            titleCell.append(colSpan);
213            titleCell.append("\" ");
214    
215            titleCell.append("class=\"tab-subhead\" ");
216    
217            titleCell.append("style=\"border-right: none;\"");
218    
219            titleCell.append(">");
220    
221            titleCell.append(buildGroupAnchor());
222    
223            titleCell.append(accountingLineGroupDefinition.getGroupLabel());
224    
225            titleCell.append("</td>");
226    
227            return titleCell.toString();
228        }
229    
230        /**
231         * Builds the unique anchor for this group
232         * 
233         * @return the unique anchor for this group
234         */
235        protected String buildGroupAnchor() {
236            return "<a name=\"accounting" + getGroupInfix() + "Anchor\"></a>";
237        }
238    
239        protected void renderGroupActions(PageContext pageContext, Tag parentTag) throws JspException {
240            List<? extends AccountingLineViewActionDefinition> accountingLineGroupActions = accountingLineGroupDefinition.getAccountingLineGroupActions();        
241            if (!this.isGroupActionsRendered() || accountingLineGroupActions == null || accountingLineGroupActions.isEmpty()) {
242                return;
243            }
244    
245            List<AccountingLineViewAction> viewActions = new ArrayList<AccountingLineViewAction>();
246            for (AccountingLineViewActionDefinition action : accountingLineGroupActions) {
247                String actionMethod = action.getActionMethod();
248                String actionLabel = action.getActionLabel();
249                String imageName = SpringContext.getBean(KualiConfigurationService.class).getPropertyString("externalizable.images.url") + action.getImageName();
250    
251                AccountingLineViewAction viewAction = new AccountingLineViewAction(actionMethod, actionLabel, imageName);
252                viewActions.add(viewAction);
253            }
254    
255            if (!viewActions.isEmpty()) {
256                ActionsRenderer actionsRenderer = new ActionsRenderer();
257                actionsRenderer.setTagBeginning(" ");
258                actionsRenderer.setTagEnding(" ");
259                actionsRenderer.setPostButtonSpacing(" ");
260                actionsRenderer.setActions(viewActions);
261                actionsRenderer.render(pageContext, parentTag);
262                actionsRenderer.clear();
263            }
264        }
265    
266        /**
267         * A dumb way to get the group infix that tries to figure out if it's dealing with a source or target line
268         * 
269         * @return the String "source" or "target" to populate the buildGroupAnchor
270         */
271        protected String getGroupInfix() {
272            Class accountingLineClass = accountingLineGroupDefinition.getAccountingLineClass();
273            return (accountingLineClass.isAssignableFrom(SourceAccountingLine.class) ? "source" : "target");
274        }
275    
276        /**
277         * Oy, the big one...this one actually renders instead of returning the HTML in a String. This is because it's kind of complex
278         * (and a likely target for future refactoring)
279         * 
280         * @param pageContext the page contex to render to
281         * @param parentTag the tag that is requesting all the rendering
282         * @throws JspException thrown if something goes wrong
283         */
284        protected void renderUploadCell(PageContext pageContext, Tag parentTag) throws JspException {
285            JspWriter out = pageContext.getOut();
286    
287            if (canUpload()) {
288                try {
289                    String hideImport = getHideImportName();
290                    String showImport = getShowImportName();
291                    String showLink = getShowLinkName();
292                    String uploadDiv = getUploadDivName();
293    
294                    out.write("\n<SCRIPT type=\"text/javascript\">\n");
295                    out.write("<!--\n");
296                    out.write("\tfunction " + hideImport + "() {\n");
297                    out.write("\t\tdocument.getElementById(\"" + showLink + "\").style.display=\"inline\";\n");
298                    out.write("\t\tdocument.getElementById(\"" + uploadDiv + "\").style.display=\"none\";\n");
299                    out.write("\t}\n");
300                    out.write("\tfunction " + showImport + "() {\n");
301                    out.write("\t\tdocument.getElementById(\"" + showLink + "\").style.display=\"none\";\n");
302                    out.write("\t\tdocument.getElementById(\"" + uploadDiv + "\").style.display=\"inline\";\n");
303                    out.write("\t}\n");
304                    out.write("\tdocument.write(\n");
305                    out.write("\t\t'<a id=\"" + showLink + "\" href=\"#\" onclick=\"" + showImport + "();return false;\">' +\n");
306                    out.write("\t\t'<img src=\"" + SpringContext.getBean(KualiConfigurationService.class).getPropertyString("externalizable.images.url") + "tinybutton-importlines.gif\" title=\"import file\" alt=\"import file\"' +\n");
307                    out.write("\t\t'width=\"72\" border=\"0\">' +\n");
308                    out.write("\t\t'</a>' +\n");
309                    out.write("\t\t'<div id=\"" + uploadDiv + "\" style=\"display:none;\" >' +\n");
310    
311                    out.write("\t\t'");
312    
313                    scriptFileTag.setPageContext(pageContext);
314                    scriptFileTag.setParent(parentTag);
315                    scriptFileTag.setProperty(accountingLineGroupDefinition.getImportedLinePropertyPrefix() + "File");
316                    scriptFileTag.doStartTag();
317                    scriptFileTag.doEndTag();
318    
319                    out.write("' +\n");
320                    out.write("\t\t'");
321    
322                    uploadButtonTag.setPageContext(pageContext);
323                    uploadButtonTag.setParent(parentTag);
324                    uploadButtonTag.setProperty("methodToCall.upload" + StringUtils.capitalize(accountingLineGroupDefinition.getImportedLinePropertyPrefix()) + "Lines");
325                    uploadButtonTag.setAlt("insert " + accountingLineGroupDefinition.getGroupLabel() + " accounting lines");
326                    uploadButtonTag.setTitle("insert " + accountingLineGroupDefinition.getGroupLabel() + " accounting lines");
327                    uploadButtonTag.doStartTag();
328                    uploadButtonTag.doEndTag();
329    
330                    out.write("' +\n");
331    
332                    out.write("\t\t'");
333    
334                    cancelButtonTag.setPageContext(pageContext);
335                    cancelButtonTag.setParent(parentTag);
336                    cancelButtonTag.setAlt("Cancel import of " + accountingLineGroupDefinition.getGroupLabel() + " accounting lines");
337                    cancelButtonTag.setTitle("Cancel import of " + accountingLineGroupDefinition.getGroupLabel() + " accounting lines");
338                    cancelButtonTag.setOnclick(getHideImportName() + "();return false;");
339                    cancelButtonTag.doStartTag();
340                    cancelButtonTag.doEndTag();
341    
342                    out.write("' +\n");
343    
344                    out.write("\t'</div>');\n");
345                    out.write("\t//-->\n");
346                    out.write("</SCRIPT>\n");
347                    out.write("<NOSCRIPT>\n");
348                    out.write("\tImport " + accountingLineGroupDefinition.getGroupLabel() + " lines\n");
349    
350                    noscriptFileTag.setPageContext(pageContext);
351                    noscriptFileTag.setParent(parentTag);
352                    noscriptFileTag.setProperty(accountingLineGroupDefinition.getImportedLinePropertyPrefix() + "File");
353                    noscriptFileTag.doStartTag();
354                    noscriptFileTag.doEndTag();
355    
356                    uploadButtonTag.doStartTag();
357                    uploadButtonTag.doEndTag();
358    
359                    out.write("</NOSCRIPT>\n");
360                }
361                catch (IOException ioe) {
362                    throw new JspException("Difficulty rendering accounting lines import upload", ioe);
363                }
364            }
365        }
366    
367        /**
368         * @return the name of the line collection property, but in a form that is okay for javascript variable/function naming
369         */
370        protected String getVariableFriendlyLineCollectionProperty() {
371            return lineCollectionProperty.replaceAll("[^A-Za-z]", "_");
372        }
373    
374        /**
375         * @return the name of the hide import function
376         */
377        protected String getHideImportName() {
378            return "hide" + getVariableFriendlyLineCollectionProperty() + "Import";
379        }
380    
381        /**
382         * @return the name of the show import function
383         */
384        protected String getShowImportName() {
385            return "show" + getVariableFriendlyLineCollectionProperty() + "Import";
386        }
387    
388        /**
389         * @return the name of the show link element
390         */
391        protected String getShowLinkName() {
392            return lineCollectionProperty + "ShowLink";
393        }
394    
395        /**
396         * @return the name of the upload div
397         */
398        protected String getUploadDivName() {
399            return "upload" + lineCollectionProperty + "Div";
400        }
401    
402        /**
403         * Determines if an upload can proceed for the accounting line group
404         * 
405         * @return true if upload is possible, false otherwise
406         */
407        protected boolean canUpload() {
408            return (canEdit && accountingDocument.getAccountingLineParser() != null && shouldUpload);
409        }
410    
411        /**
412         * Allows overriding of whether something can be uploaded - though this serves only to turn uploading more off, never more on
413         * 
414         * @param allowUpload should we be allowed to upload?
415         */
416        public void overrideCanUpload(boolean allowUpload) {
417            this.shouldUpload = allowUpload;
418        }
419    
420        /**
421         * Gets the cellCount attribute.
422         * 
423         * @return Returns the cellCount.
424         */
425        public int getCellCount() {
426            return cellCount;
427        }
428    
429        /**
430         * Sets the cellCount attribute value.
431         * 
432         * @param cellCount The cellCount to set.
433         */
434        public void setCellCount(int cellCount) {
435            this.cellCount = cellCount;
436        }
437    
438        /**
439         * Gets the accountingDocument attribute.
440         * 
441         * @return Returns the accountingDocument.
442         */
443        public AccountingDocument getAccountingDocument() {
444            return accountingDocument;
445        }
446    
447        /**
448         * Sets the accountingDocument attribute value.
449         * 
450         * @param accountingDocument The accountingDocument to set.
451         */
452        public void setAccountingDocument(AccountingDocument accountingDocument) {
453            this.accountingDocument = accountingDocument;
454        }
455    
456        /**
457         * Gets the accountingLineGroupDefinition attribute.
458         * 
459         * @return Returns the accountingLineGroupDefinition.
460         */
461        public AccountingLineGroupDefinition getAccountingLineGroupDefinition() {
462            return accountingLineGroupDefinition;
463        }
464    
465        /**
466         * Sets the accountingLineGroupDefinition attribute value.
467         * 
468         * @param accountingLineGroupDefinition The accountingLineGroupDefinition to set.
469         */
470        public void setAccountingLineGroupDefinition(AccountingLineGroupDefinition accountingLineGroupDefinition) {
471            this.accountingLineGroupDefinition = accountingLineGroupDefinition;
472        }
473    
474        /**
475         * Gets the titleCellSpan attribute.
476         * 
477         * @return Returns the titleCellSpan.
478         */
479        public int getTitleCellSpan() {
480            return titleCellSpan;
481        }
482    
483        /**
484         * Sets the titleCellSpan attribute value.
485         * 
486         * @param titleCellSpan The titleCellSpan to set.
487         */
488        public void setTitleCellSpan(int titleCellSpan) {
489            this.titleCellSpan = titleCellSpan;
490        }
491    
492        /**
493         * Gets the lineCollectionProperty attribute.
494         * 
495         * @return Returns the lineCollectionProperty.
496         */
497        public String getLineCollectionProperty() {
498            return lineCollectionProperty;
499        }
500    
501        /**
502         * Sets the lineCollectionProperty attribute value.
503         * 
504         * @param lineCollectionProperty The lineCollectionProperty to set.
505         */
506        public void setLineCollectionProperty(String lineCollectionProperty) {
507            this.lineCollectionProperty = lineCollectionProperty;
508        }
509    
510        /**
511         * Gets the groupActionsRendered attribute.
512         * 
513         * @return Returns the groupActionsRendered.
514         */
515        public boolean isGroupActionsRendered() {
516            return groupActionsRendered;
517        }
518    
519        /**
520         * Sets the groupActionsRendered attribute value.
521         * 
522         * @param groupActionsRendered The groupActionsRendered to set.
523         */
524        public void setGroupActionsRendered(boolean groupActionsRenderred) {
525            this.groupActionsRendered = groupActionsRenderred;
526        }
527    
528        /**
529         * Sets the canEdit attribute value.
530         * @param canEdit The canEdit to set.
531         */
532        public void setCanEdit(boolean canEdit) {
533            this.canEdit = canEdit;
534        }
535    
536    }