001/**
002 * Copyright 2005-2012 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 */
016package org.kuali.rice.krad.web.form;
017
018import org.apache.commons.lang.StringUtils;
019import org.codehaus.jackson.map.ObjectMapper;
020import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
021import org.kuali.rice.krad.uif.UifConstants;
022import org.kuali.rice.krad.uif.UifParameters;
023import org.kuali.rice.krad.uif.view.History;
024import org.kuali.rice.krad.uif.view.View;
025import org.kuali.rice.krad.uif.service.ViewService;
026import org.kuali.rice.krad.uif.view.ViewModel;
027import org.kuali.rice.krad.util.KRADUtils;
028import org.springframework.web.multipart.MultipartFile;
029import org.kuali.rice.krad.uif.UifConstants.ViewType;
030
031import javax.servlet.http.HttpServletRequest;
032import java.io.IOException;
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037import java.util.Properties;
038import java.util.Set;
039import java.util.UUID;
040
041/**
042 * Base form class for views within the KRAD User Interface Framework
043 *
044 * <p>
045 * Holds properties necessary to determine the <code>View</code> instance that
046 * will be used to render the UI
047 * </p>
048 *
049 * @author Kuali Rice Team (rice.collab@kuali.org)
050 */
051public class UifFormBase implements ViewModel {
052    private static final long serialVersionUID = 8432543267099454434L;
053
054    // current view
055    protected String viewId;
056    protected String viewName;
057    protected ViewType viewTypeName;
058    protected String pageId;
059    protected String methodToCall;
060    protected String formKey;
061    protected String jumpToId;
062    protected String jumpToName;
063    protected String focusId;
064    protected String formPostUrl;
065
066    protected boolean defaultsApplied;
067
068    protected View view;
069    protected View previousView;
070
071    protected Map<String, String> viewRequestParameters;
072    protected List<String> readOnlyFieldsList;
073
074    protected Map<String, Object> newCollectionLines;
075    protected Map<String, String> actionParameters;
076    protected Map<String, Object> clientStateForSyncing;
077    protected Map<String, Set<String>> selectedCollectionLines;
078
079    protected MultipartFile attachmentFile;
080
081    // navigation
082    protected String returnLocation;
083    protected String returnFormKey;
084
085    protected History formHistory;
086
087    protected boolean renderFullView;
088    protected boolean validateDirty;
089
090    public UifFormBase() {
091        formKey = generateFormKey();
092        renderFullView = true;
093        defaultsApplied = false;
094
095        formHistory = new History();
096
097        readOnlyFieldsList = new ArrayList<String>();
098        viewRequestParameters = new HashMap<String, String>();
099        newCollectionLines = new HashMap<String, Object>();
100        actionParameters = new HashMap<String, String>();
101        clientStateForSyncing = new HashMap<String, Object>();
102        selectedCollectionLines = new HashMap<String, Set<String>>();
103    }
104
105    /**
106     * Creates the unique id used to store this "conversation" in the session.
107     * The default method generates a java UUID.
108     *
109     * @return
110     */
111    protected String generateFormKey() {
112        return UUID.randomUUID().toString();
113    }
114
115    /**
116     * Called after Spring binds the request to the form and before the
117     * controller method is invoked.
118     *
119     * @param request - request object containing the query parameters
120     */
121    public void postBind(HttpServletRequest request) {
122        // default form post URL to request URL
123        formPostUrl = request.getRequestURL().toString();
124
125        // get any sent client view state and parse into map
126        if (request.getParameterMap().containsKey(UifParameters.CLIENT_VIEW_STATE)) {
127            String clientStateJSON = request.getParameter(UifParameters.CLIENT_VIEW_STATE);
128            if (StringUtils.isNotBlank(clientStateJSON)) {
129                // change single quotes to double quotes (necessary because the reverse was done for sending)
130                clientStateJSON = StringUtils.replace(clientStateJSON, "'", "\"");
131
132                ObjectMapper mapper = new ObjectMapper();
133                try {
134                    clientStateForSyncing = mapper.readValue(clientStateJSON, Map.class);
135                } catch (IOException e) {
136                    throw new RuntimeException("Unable to decode client side state JSON", e);
137                }
138            }
139        }
140
141        // populate read only fields list
142        if (request.getParameter(UifParameters.READ_ONLY_FIELDS) != null) {
143            String readOnlyFields = request.getParameter(UifParameters.READ_ONLY_FIELDS);
144            setReadOnlyFieldsList(KRADUtils.convertStringParameterToList(readOnlyFields));
145        }
146    }
147
148    /**
149     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewId()
150     */
151    public String getViewId() {
152        return this.viewId;
153    }
154
155    /**
156     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewId(java.lang.String)
157     */
158    public void setViewId(String viewId) {
159        this.viewId = viewId;
160    }
161
162    /**
163     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewName()
164     */
165    public String getViewName() {
166        return this.viewName;
167    }
168
169    /**
170     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewName(java.lang.String)
171     */
172    public void setViewName(String viewName) {
173        this.viewName = viewName;
174    }
175
176    /**
177     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewTypeName()
178     */
179    public ViewType getViewTypeName() {
180        return this.viewTypeName;
181    }
182
183    /**
184     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewTypeName(org.kuali.rice.krad.uif.UifConstants.ViewType)
185     */
186    public void setViewTypeName(ViewType viewTypeName) {
187        this.viewTypeName = viewTypeName;
188    }
189
190    /**
191     * @see org.kuali.rice.krad.uif.view.ViewModel#getPageId()
192     */
193    public String getPageId() {
194        return this.pageId;
195    }
196
197    /**
198     * @see org.kuali.rice.krad.uif.view.ViewModel#setPageId(java.lang.String)
199     */
200    public void setPageId(String pageId) {
201        this.pageId = pageId;
202    }
203
204    /**
205     * @see org.kuali.rice.krad.uif.view.ViewModel#getFormPostUrl()
206     */
207    public String getFormPostUrl() {
208        return this.formPostUrl;
209    }
210
211    /**
212     * @see org.kuali.rice.krad.uif.view.ViewModel#setFormPostUrl(java.lang.String)
213     */
214    public void setFormPostUrl(String formPostUrl) {
215        this.formPostUrl = formPostUrl;
216    }
217
218    public String getReturnLocation() {
219        return this.returnLocation;
220    }
221
222    public void setReturnLocation(String returnLocation) {
223        this.returnLocation = returnLocation;
224    }
225
226    public String getReturnFormKey() {
227        return this.returnFormKey;
228    }
229
230    public void setReturnFormKey(String returnFormKey) {
231        this.returnFormKey = returnFormKey;
232    }
233
234    /**
235     * Identifies the controller method that should be invoked to fulfill a
236     * request. The value will be matched up against the 'params' setting on the
237     * <code>RequestMapping</code> annotation for the controller method
238     *
239     * @return String method to call
240     */
241    public String getMethodToCall() {
242        return this.methodToCall;
243    }
244
245    /**
246     * Setter for the method to call
247     *
248     * @param methodToCall
249     */
250    public void setMethodToCall(String methodToCall) {
251        this.methodToCall = methodToCall;
252    }
253
254    /**
255     * @see org.kuali.rice.krad.uif.view.ViewModel#getViewRequestParameters()
256     */
257    public Map<String, String> getViewRequestParameters() {
258        return this.viewRequestParameters;
259    }
260
261    /**
262     * @see org.kuali.rice.krad.uif.view.ViewModel#setViewRequestParameters(java.util.Map<java.lang.String,java.lang.String>)
263     */
264    public void setViewRequestParameters(Map<String, String> viewRequestParameters) {
265        this.viewRequestParameters = viewRequestParameters;
266    }
267
268    /**
269     * @see org.kuali.rice.krad.uif.view.ViewModel#getReadOnlyFieldsList()
270     */
271    public List<String> getReadOnlyFieldsList() {
272        return readOnlyFieldsList;
273    }
274
275    /**
276     * @see org.kuali.rice.krad.uif.view.ViewModel#setReadOnlyFieldsList(java.util.List<java.lang.String>)
277     */
278    public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) {
279        this.readOnlyFieldsList = readOnlyFieldsList;
280    }
281
282    /**
283     * @see org.kuali.rice.krad.uif.view.ViewModel#getNewCollectionLines()
284     */
285    public Map<String, Object> getNewCollectionLines() {
286        return this.newCollectionLines;
287    }
288
289    /**
290     * @see org.kuali.rice.krad.uif.view.ViewModel#setNewCollectionLines(java.util.Map<java.lang.String,java.lang.Object>)
291     */
292    public void setNewCollectionLines(Map<String, Object> newCollectionLines) {
293        this.newCollectionLines = newCollectionLines;
294    }
295
296    /**
297     * @see org.kuali.rice.krad.uif.view.ViewModel#getActionParameters()
298     */
299    public Map<String, String> getActionParameters() {
300        return this.actionParameters;
301    }
302
303    /**
304     * Returns the action parameters map as a <code>Properties</code> instance
305     *
306     * @return Properties action parameters
307     */
308    public Properties getActionParametersAsProperties() {
309        return KRADUtils.convertMapToProperties(actionParameters);
310    }
311
312    /**
313     * @see org.kuali.rice.krad.uif.view.ViewModel#setActionParameters(java.util.Map<java.lang.String,java.lang.String>)
314     */
315    public void setActionParameters(Map<String, String> actionParameters) {
316        this.actionParameters = actionParameters;
317    }
318
319    /**
320     * Retrieves the value for the given action parameter, or empty string if
321     * not found
322     *
323     * @param actionParameterName - name of the action parameter to retrieve value for
324     * @return String parameter value or empty string
325     */
326    public String getActionParamaterValue(String actionParameterName) {
327        if ((actionParameters != null) && actionParameters.containsKey(actionParameterName)) {
328            return actionParameters.get(actionParameterName);
329        }
330
331        return "";
332    }
333
334    /**
335     * Returns the action event that was sent in the action parameters (if any)
336     *
337     * <p>
338     * The action event is a special action parameter that can be sent to indicate a type of action being taken. This
339     * can be looked at by the view or components to render differently
340     * </p>
341     *
342     * TODO: make sure action parameters are getting reinitialized on each request
343     *
344     * @return String action event name or blank if action event was not sent
345     */
346    public String getActionEvent() {
347        if ((actionParameters != null) && actionParameters.containsKey(UifConstants.UrlParams.ACTION_EVENT)) {
348            return actionParameters.get(UifConstants.UrlParams.ACTION_EVENT);
349        }
350
351        return "";
352    }
353
354    /**
355     * @see org.kuali.rice.krad.uif.view.ViewModel#getClientStateForSyncing()
356     */
357    public Map<String, Object> getClientStateForSyncing() {
358        return clientStateForSyncing;
359    }
360
361    /**
362     * @see org.kuali.rice.krad.uif.view.ViewModel#getSelectedCollectionLines()
363     */
364    public Map<String, Set<String>> getSelectedCollectionLines() {
365        return selectedCollectionLines;
366    }
367
368    /**
369     * @see org.kuali.rice.krad.uif.view.ViewModel#setSelectedCollectionLines(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)
370     */
371    public void setSelectedCollectionLines(Map<String, Set<String>> selectedCollectionLines) {
372        this.selectedCollectionLines = selectedCollectionLines;
373    }
374
375    /**
376     * Key string that identifies the form instance in session storage
377     *
378     * <p>
379     * When the view is posted, the previous form instance is retrieved and then
380     * populated from the request parameters. This key string is retrieve the
381     * session form from the session service
382     * </p>
383     *
384     * @return String form session key
385     */
386    public String getFormKey() {
387        return this.formKey;
388    }
389
390    /**
391     * Setter for the form's session key
392     *
393     * @param formKey
394     */
395    public void setFormKey(String formKey) {
396        this.formKey = formKey;
397    }
398
399    /**
400     * @see org.kuali.rice.krad.uif.view.ViewModel#isDefaultsApplied()
401     */
402    public boolean isDefaultsApplied() {
403        return this.defaultsApplied;
404    }
405
406    /**
407     * @see org.kuali.rice.krad.uif.view.ViewModel#setDefaultsApplied(boolean)
408     */
409    public void setDefaultsApplied(boolean defaultsApplied) {
410        this.defaultsApplied = defaultsApplied;
411    }
412
413    /**
414     * Holder for files that are attached through the view
415     *
416     * @return MultipartFile representing the attachment
417     */
418    public MultipartFile getAttachmentFile() {
419        return this.attachmentFile;
420    }
421
422    /**
423     * Setter for the form's attachment file
424     *
425     * @param attachmentFile
426     */
427    public void setAttachmentFile(MultipartFile attachmentFile) {
428        this.attachmentFile = attachmentFile;
429    }
430
431    /**
432     * @return the renderFullView
433     */
434    public boolean isRenderFullView() {
435        return this.renderFullView;
436    }
437
438    /**
439     * @param renderFullView
440     */
441    public void setRenderFullView(boolean renderFullView) {
442        this.renderFullView = renderFullView;
443    }
444
445    /**
446     * @see org.kuali.rice.krad.uif.view.ViewModel#getView()
447     */
448    public View getView() {
449        return this.view;
450    }
451
452    /**
453     * @see org.kuali.rice.krad.uif.view.ViewModel#setView(org.kuali.rice.krad.uif.view.View)
454     */
455    public void setView(View view) {
456        this.view = view;
457    }
458
459    /**
460     * @see org.kuali.rice.krad.uif.view.ViewModel#getPreviousView()
461     */
462    public View getPreviousView() {
463        return this.previousView;
464    }
465
466    /**
467     * @see org.kuali.rice.krad.uif.view.ViewModel#setPreviousView(org.kuali.rice.krad.uif.view.View)
468     */
469    public void setPreviousView(View previousView) {
470        this.previousView = previousView;
471    }
472
473    /**
474     * Instance of the <code>ViewService</code> that can be used to retrieve
475     * <code>View</code> instances
476     *
477     * @return ViewService implementation
478     */
479    protected ViewService getViewService() {
480        return KRADServiceLocatorWeb.getViewService();
481    }
482
483    /**
484     * The jumpToId for this form, the element with this id will be jumped to automatically
485     * when the form is loaded in the view.
486     * Using "TOP" or "BOTTOM" will jump to the top or the bottom of the resulting page.
487     * jumpToId always takes precedence over jumpToName, if set.
488     *
489     * @return the jumpToId
490     */
491    public String getJumpToId() {
492        return this.jumpToId;
493    }
494
495    /**
496     * @param jumpToId the jumpToId to set
497     */
498    public void setJumpToId(String jumpToId) {
499        this.jumpToId = jumpToId;
500    }
501
502    /**
503     * The jumpToName for this form, the element with this name will be jumped to automatically
504     * when the form is loaded in the view.
505     * WARNING: jumpToId always takes precedence over jumpToName, if set.
506     *
507     * @return the jumpToName
508     */
509    public String getJumpToName() {
510        return this.jumpToName;
511    }
512
513    /**
514     * @param jumpToName the jumpToName to set
515     */
516    public void setJumpToName(String jumpToName) {
517        this.jumpToName = jumpToName;
518    }
519
520    /**
521     * Field to place focus on when the page loads
522     * An empty focusId will result in focusing on the first visible input element by default.
523     *
524     * @return the focusId
525     */
526    public String getFocusId() {
527        return this.focusId;
528    }
529
530    /**
531     * @param focusId the focusId to set
532     */
533    public void setFocusId(String focusId) {
534        this.focusId = focusId;
535    }
536
537    /**
538     * History parameter representing the History of views that have come before the
539     * viewing of the current view
540     *
541     * <p>
542     * Used for breadcrumb widget generation on the view and also for navigating back
543     * to previous or hub locations
544     * </p>
545     *
546     * @return History instance giving current history
547     */
548    public History getFormHistory() {
549        return formHistory;
550    }
551
552    /**
553     * Setter for the current History object
554     *
555     * @param history the history to set
556     */
557    public void setFormHistory(History history) {
558        this.formHistory = history;
559    }
560
561    /**
562     * Indicates whether the form should be validated for dirtyness
563     *
564     * <p>
565     * For FormView, it's necessary to validate when the user tries to navigate out of the form. If set, all the
566     * InputFields will be validated on refresh, navigate, cancel or close Action or on form
567     * unload and if dirty, displays a message and user can decide whether to continue with
568     * the action or stay on the form
569     * </p>
570     *
571     * @return boolean true if dirty validation should be enabled
572     */
573    public boolean isValidateDirty() {
574        return this.validateDirty;
575    }
576
577    /**
578     * Setter for dirty validation indicator
579     *
580     * @param validateDirty
581     */
582    public void setValidateDirty(boolean validateDirty) {
583        this.validateDirty = validateDirty;
584    }
585
586}