/
Widgets.hs
1359 lines (1168 loc) · 47.8 KB
/
Widgets.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{- |
Some dynamic widgets, widgets that dynamically edit content in other widgets,
widgets for templating, content management and multilanguage. And some primitives
to create other active widgets.
-}
-- {-# OPTIONS -F -pgmF cpphs #-}
{-# OPTIONS -cpp -pgmPcpphs -optP--cpp #-}
{-# LANGUAGE UndecidableInstances,ExistentialQuantification
, FlexibleInstances, OverlappingInstances, FlexibleContexts
, OverloadedStrings, DeriveDataTypeable , ScopedTypeVariables
, StandaloneDeriving #-}
module MFlow.Forms.Widgets (
-- * Ajax refreshing of widgets
autoRefresh, noAutoRefresh, appendUpdate, prependUpdate, push, UpdateMethod(..), lazy
-- * JQueryUi widgets
,datePicker, getSpinner, wautocomplete, wdialog,
-- * User Management
userFormOrName,maybeLogout, wlogin,
-- * Active widgets
wEditList,wautocompleteList
, wautocompleteEdit,
-- * Editing widgets
delEdited, getEdited, setEdited, prependWidget,appendWidget,setWidget
-- * Content Management
,tField, tFieldEd, htmlEdit, edTemplate, dField, template, witerate,tfieldKey
-- * Multilanguage
,mFieldEd, mField
-- * utility
,insertForm, readtField, writetField
) where
import MFlow
import MFlow.Forms
import MFlow.Forms.Internals
import Data.Monoid
import Data.ByteString.Lazy.UTF8 as B hiding (length,span)
import Data.ByteString.Lazy.Char8 (unpack)
import Control.Monad.Trans
import Data.Typeable
import Data.List
import System.IO.Unsafe
import Control.Monad.State
import Data.TCache
import Data.TCache.Defs
import Data.TCache.Memoization
import Data.RefSerialize hiding ((<|>))
import qualified Data.Map as M
import Data.IORef
import MFlow.Cookies
import Data.Maybe
import Data.Char
import Control.Monad.Identity
import Control.Workflow(killWF)
import Unsafe.Coerce
import Control.Exception
import MFlow.Forms.Cache
--jqueryScript= "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"
--jqueryScript1="//code.jquery.com/jquery-1.9.1.js"
--
--jqueryCSS1= "//code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css"
--jqueryCSS= "//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css"
--
--jqueryUI1= "//code.jquery.com/ui/1.9.1/jquery-ui.js"
--jqueryUI= "//code.jquery.com/ui/1.10.3/jquery-ui.js"
jqueryScript= getConfig "cjqueryScript" "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"
jqueryCSS= getConfig "cjqueryCSS" "//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css"
jqueryUI= getConfig "cjqueryUI" "//code.jquery.com/ui/1.10.3/jquery-ui.js"
nicEditUrl= getConfig "cnicEditUrl" "//js.nicedit.com/nicEdit-latest.js"
------- User Management ------
-- | Present a user form if not logged in. Otherwise, the user name and a logout link is presented.
-- The parameters and the behavior are the same as 'userWidget'.
-- Only the display is different
userFormOrName mode wid= userWidget mode wid `wmodify` f <** maybeLogout
where
f _ justu@(Just u) = return ([fromStr u], justu) -- !> "input"
f felem Nothing = do
us <- getCurrentUser -- getEnv cookieuser
if us == anonymous
then return (felem, Nothing)
else return([fromStr us], Just us)
-- | Display a logout link if the user is logged. Nothing otherwise
maybeLogout :: (MonadIO m,Functor m,FormInput v) => View v m ()
maybeLogout= do
us <- getCurrentUser
if us/= anonymous
then do
cmd <- ajax $ const $ return "window.location=='/'" --refresh
fromStr " " ++> ((wlink () (fromStr "logout")) <![("onclick",cmd "''")]) `waction` const logout
else noWidget
data Medit view m a = Medit (M.Map B.ByteString [(String,View view m a)])
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
deriving Typeable
#else
instance (Typeable view, Typeable a) => Typeable (Medit view m a) where
typeOf= \v -> mkTyConApp (mkTyCon3 "MFlow" "MFlow.Forms.Widgets" "Medit" )
[typeOf (tview v)
,typeOf (ta v)]
where
tview :: Medit v m a -> v
tview= undefined
tm :: Medit v m a -> m a
tm= undefined
ta :: Medit v m a -> a
ta= undefined
#endif
-- | If not logged, it present a page flow which asks for the user name, then the password if not logged
--
-- If logged, it present the user name and a link to logout
--
-- normally to be used with autoRefresh and pageFlow when used with other widgets.
wlogin :: (MonadIO m,Functor m,FormInput v) => View v m ()
wlogin= wform $ do
username <- getCurrentUser
if username /= anonymous
then do
private; noCache;noStore
return username
else do
name <- getString Nothing <! hint "login name"
<! size (9 :: Int)
<++ ftag "br" mempty
pass <- getPassword <! hint "password"
<! size 9
<++ ftag "br" mempty
<** submitButton "login"
val <- userValidate (name,pass)
case val of
Just msg -> notValid msg
Nothing -> login name >> clearEnv' >> (return name)
`wcallback` (\name -> ftag "b" (fromStr $ "logged as " ++ name++ " ")
++> pageFlow "logout" (submitButton "logout")) -- wlink ("logout" :: String) (ftag "b" $ fromStr " logout"))
`wcallback` const (logout >> clearEnv' >> wlogin)
focus = [("onload","this.focus()")]
hint s= [("placeholder",s)]
size n= [("size",show n)]
getEdited1 id= do
Medit stored <- getSessionData `onNothing` return (Medit M.empty)
return $ fromMaybe [] $ M.lookup id stored
-- | Return the list of edited widgets (added by the active widgets) for a given identifier
getEdited
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
:: (Typeable v, Typeable a, Typeable m1, MonadState (MFlowState view) m) =>
#else
:: (Typeable v, Typeable a, MonadState (MFlowState view) m) =>
#endif
B.ByteString -> m [View v m1 a]
getEdited id= do
r <- getEdited1 id
let (_,ws)= unzip r
return ws
-- | Deletes the list of edited widgets for a certain identifier and with the type of the witness widget parameter
delEdited
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
:: (Typeable v, Typeable a, MonadIO m, Typeable m1,
#else
:: (Typeable v, Typeable a, MonadIO m,
#endif
MonadState (MFlowState view) m)
=> B.ByteString -- ^ identifier
-> [View v m1 a] -> m () -- ^ witness
delEdited id witness=do
Medit stored <- getSessionData `onNothing` return (Medit (M.empty))
let (ks, ws)= unzip $ fromMaybe [] $ M.lookup id stored
return $ ws `asTypeOf` witness
liftIO $ mapM flushCached ks
let stored'= M.delete id stored
setSessionData . Medit $ stored'
-- setEdited id ([] `asTypeOf` (zip (repeat "") witness))
setEdited id ws= do
Medit stored <- getSessionData `onNothing` return (Medit (M.empty))
let stored'= M.insert id ws stored
setSessionData . Medit $ stored'
addEdited id w= do
ws <- getEdited1 id
setEdited id (w:ws)
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
modifyWidget :: (MonadIO m,Executable m,Typeable a,FormInput v, Typeable Identity, Typeable m)
#else
modifyWidget :: (MonadIO m,Executable m,Typeable a,FormInput v)
#endif
=> B.ByteString -> B.ByteString -> View v Identity a -> View v m B.ByteString
modifyWidget selector modifier w = View $ do
ws <- getEdited selector
let n = length (ws `asTypeOf` [w])
let key= "widget"++ show selector ++ show n ++ show (typeOf $ typ w)
let cw = wcached key 0 w
addEdited selector (key,cw)
FormElm form _ <- runView cw
let elem= toByteString form
return . FormElm mempty . Just $ selector <> "." <> modifier <> "('" <> elem <> "');"
where
typ :: View v m a -> a
typ = undefined
-- | Return the JavaScript to be executed on the browser to prepend a widget to the location
-- identified by the selector (the bytestring parameter), The selector must have the form of a jQuery expression
-- . It stores the added widgets in the edited list, that is accessed with 'getEdited'
--
-- The resulting string can be executed in the browser. 'ajax' will return the code to
-- execute the complete ajax roundtrip. This code returned by ajax must be in an event handler.
--
-- This example will insert a widget in the div when the element with identifier
-- /clickelem/ is clicked. when the form is submitted, the widget values are returned
-- and the list of edited widgets are deleted.
--
-- > id1<- genNewId
-- > let sel= "$('#" <> fromString id1 <> "')"
-- > callAjax <- ajax . const $ prependWidget sel wn
-- > let installevents= "$(document).ready(function(){\
-- > \$('#clickelem').click(function(){"++callAjax "''"++"});})"
-- >
-- > requires [JScriptFile jqueryScript [installevents] ]
-- > ws <- getEdited sel
-- > r <- (div <<< manyOf ws) <! [("id",id1)]
-- > delEdited sel ws'
-- > return r
prependWidget
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
:: (Typeable a, MonadIO m, Executable m, FormInput v, Typeable Identity, Typeable m)
#else
:: (Typeable a, MonadIO m, Executable m, FormInput v)
#endif
=> B.ByteString -- ^ jQuery selector
-> View v Identity a -- ^ widget to prepend
-> View v m B.ByteString -- ^ string returned with the jQuery string to be executed in the browser
prependWidget sel w= modifyWidget sel "prepend" w
-- | Like 'prependWidget' but append the widget instead of prepend.
appendWidget
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
:: (Typeable a, MonadIO m, Executable m, FormInput v, Typeable Identity, Typeable m) =>
#else
:: (Typeable a, MonadIO m, Executable m, FormInput v) =>
#endif
B.ByteString -> View v Identity a -> View v m B.ByteString
appendWidget sel w= modifyWidget sel "append" w
-- | Like 'prependWidget' but set the entire content of the selector instead of prepending an element
setWidget
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
:: (Typeable a, MonadIO m, Executable m, FormInput v, Typeable Identity, Typeable m) =>
#else
:: (Typeable a, MonadIO m, Executable m, FormInput v) =>
#endif
B.ByteString -> View v Identity a -> View v m B.ByteString
setWidget sel w= modifyWidget sel "html" w
-- | Inside a tag, it add and delete widgets of the same type. When the form is submitted
-- or a wlink is pressed, this widget return the list of validated widgets.
-- the event for adding a new widget is attached , as a click event to the element of the page with the identifier /wEditListAdd/
-- that the user will choose.
--
-- This example add or delete editable text boxes, with two initial boxes with
-- /hi/, /how are you/ as values. Tt uses blaze-html:
--
-- > r <- ask $ addLink
-- > ++> br
-- > ++> (El.div `wEditList` getString1 $ ["hi", "how are you"]) "addid"
-- > <++ br
-- > <** submitButton "send"
-- >
-- > ask $ p << (show r ++ " returned")
-- > ++> wlink () (p << text " back to menu")
-- > mainmenu
-- > where
-- > addLink = a ! At.id "addid"
-- > ! href "#"
-- > $ text "add"
-- > delBox = input ! type_ "checkbox"
-- > ! checked ""
-- > ! onclick "this.parentNode.parentNode.removeChild(this.parentNode)"
-- > getString1 mx= El.div <<< delBox ++> getString mx <++ br
wEditList :: (Typeable a,Read a
,FormInput view
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
,Functor m,MonadIO m, Executable m, Typeable m, Typeable Identity)
#else
,Functor m,MonadIO m, Executable m)
#endif
=> (view ->view) -- ^ The holder tag
-> (Maybe a -> View view Identity a) -- ^ the contained widget, initialized by a value of its type
-> [a] -- ^ The initial list of values.
-> String -- ^ The id of the button or link that will create a new list element when clicked
-> View view m [a]
wEditList holderview w xs addId = pageFlow addId $ do
let ws= map (w . Just) xs
wn= w Nothing
id1<- genNewId
let sel= "$('#" <> fromString id1 <> "')"
callAjax <- ajax . const $ prependWidget sel wn
let installevents= "$(document).ready(function(){$('#"++addId++"').click(function(){"++callAjax "''"++"});})"
requires [JScriptFile jqueryScript [installevents] ]
ws' <- getEdited sel
r <- (holderview <<< (manyOf $ ws' ++ map changeMonad ws)) <! [("id",id1)]
delEdited sel ws'
return r
-- | Present the JQuery auto-completion list, from a procedure defined by the programmer, to a text box.
wautocomplete
:: (Show a, MonadIO m, FormInput v)
=> Maybe String -- ^ Initial value
-> (String -> IO a) -- ^ Auto-completion procedure: will receive a prefix and return a list of strings
-> View v m String
wautocomplete mv autocomplete = do
text1 <- genNewId
ajaxc <- ajax $ \u -> do
r <- liftIO $ autocomplete u
return $ jaddtoautocomp text1 r
requires [JScriptFile jqueryScript [] -- [events]
,CSSFile jqueryCSS
,JScriptFile jqueryUI []]
getString mv <! [("type", "text")
,("id", text1)
,("oninput", ajaxc $ "$('#"++text1++"').attr('value')" )
,("autocomplete", "off")]
where
jaddtoautocomp text1 us= "$('#"<>fromString text1<>"').autocomplete({ source: " <> fromString( show us) <> " });"
-- | Produces a text box. It gives a auto-completion list to the textbox. When return
-- is pressed in the textbox, the box content is used to create a widget of a kind defined
-- by the user, which will be situated above of the textbox. When submitted, the result is the content
-- of the created widgets (the validated ones).
--
-- 'wautocompleteList' is an specialization of this widget, where
-- the widget parameter is fixed, with a checkbox that delete the element when unselected
-- . This fixed widget is as such (using generic 'FormElem' class tags):
--
-- > ftag "div" <<< ftag "input" mempty
-- > `attrs` [("type","checkbox")
-- > ,("checked","")
-- > ,("onclick","this.parentNode.parentNode.removeChild(this.parentNode)")]
-- > ++> ftag "span" (fromStr $ fromJust x )
-- > ++> whidden( fromJust x)
wautocompleteEdit
:: (Typeable a, MonadIO m,Functor m, Executable m
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
, FormInput v, Typeable m, Typeable Identity)
#else
, FormInput v)
#endif
=> String -- ^ the initial text of the box
-> (String -> IO [String]) -- ^ the auto-completion procedure: receives a prefix, return a list of options.
-> (Maybe String -> View v Identity a) -- ^ the widget to add, initialized with the string entered in the box
-> [String] -- ^ initial set of values
-> View v m [a] -- ^ resulting widget
wautocompleteEdit phold autocomplete elem values= do
id1 <- genNewId
let textx= id1++"text"
let sel= "$('#" <> fromString id1 <> "')"
ajaxc <- ajax $ \(c:u) ->
case c of
'f' -> prependWidget sel (elem $ Just u)
_ -> do
r <- liftIO $ autocomplete u
return $ jaddtoautocomp textx r
requires [JScriptFile jqueryScript [events textx ajaxc]
,CSSFile jqueryCSS
,JScriptFile jqueryUI []]
ws' <- getEdited sel
r<- (ftag "div" mempty `attrs` [("id", id1)]
++> manyOf (ws' ++ (map (changeMonad . elem . Just) values)))
<++ ftag "input" mempty
`attrs` [("type", "text")
,("id", textx)
,("placeholder", phold)
,("oninput", ajaxc $ "'n'+$('#"++textx++"').val()" )
,("autocomplete", "off")]
delEdited sel ws'
return r
where
events textx ajaxc=
"$(document).ready(function(){ \
\$('#"++textx++"').keydown(function(){ \
\if(event.keyCode == 13){ \
\var v= $('#"++textx++"').val(); \
\if(event.preventDefault) event.preventDefault();\
\else if(event.returnValue) event.returnValue = false;" ++
ajaxc "'f'+v"++";"++
" $('#"++textx++"').val('');\
\}\
\});\
\});"
jaddtoautocomp textx us= "$('#"<>fromString textx<>"').autocomplete({ source: " <> fromString( show us) <> " });"
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
deriving instance Typeable Identity
#endif
-- | A specialization of 'wutocompleteEdit' which make appear each chosen option with
-- a checkbox that deletes the element when unchecked. The result, when submitted, is the list of selected elements.
wautocompleteList
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
:: (Functor m, MonadIO m, Executable m, FormInput v, Typeable m, Typeable Identity) =>
#else
:: (Functor m, MonadIO m, Executable m, FormInput v) =>
#endif
String -> (String -> IO [String]) -> [String] -> View v m [String]
wautocompleteList phold serverproc values=
wautocompleteEdit phold serverproc wrender1 values
where
wrender1 x= ftag "div" <<< ftag "input" mempty
`attrs` [("type","checkbox")
,("checked","")
,("onclick","this.parentNode.parentNode.removeChild(this.parentNode)")]
++> ftag "span" (fromStr $ fromJust x )
++> whidden( fromJust x)
------- Templating and localization ---------
data TField = TField {tfieldKey :: Key, tfieldContent :: B.ByteString} deriving (Read, Show,Typeable)
instance Indexable TField where
key (TField k _)= k
defPath _= "texts/"
instance Serializable TField where
serialize (TField k content) = content
deserialKey k content= TField k content -- applyDeserializers [des1,des2] k bs
setPersist = \_ -> Just filePersist
writetField k s= atomically $ writeDBRef (getDBRef k) $ TField k $ toByteString s
readtField text k= atomically $ do
let ref = getDBRef k
mr <- readDBRef ref
case mr of
Just (TField k v) -> if v /= mempty then return $ fromStrNoEncode $ toString v else return text
Nothing -> return text
-- | Creates a rich text editor around a text field or a text area widget.
-- This code:
--
-- > page $ p "Insert the text"
-- > ++> htmlEdit ["bold","italic"] ""
-- > (getMultilineText "" <! [("rows","3"),("cols","80")]) <++ br
-- > <** submitButton "enter"
--
-- Creates a rich text area with bold and italic buttons. The buttons are the ones added
-- in the nicEdit editor.
htmlEdit :: (Monad m, FormInput v) => [String] -> UserStr -> View v m a -> View v m a
htmlEdit buttons jsuser w = do
id <- genNewId
let installHtmlField=
"\nfunction installHtmlField(muser,cookieuser,name,buttons){\
\if(muser== '' || document.cookie.search(cookieuser+'='+muser) != -1)\
\bkLib.onDomLoaded(function() {\
\var myNicEditor = new nicEditor({buttonList : buttons});\
\myNicEditor.panelInstance(name);\
\})};\n"
install= "installHtmlField('"++jsuser++"','"++cookieuser++"','"++id++"',"++show buttons++");\n"
requires [JScript installHtmlField ,JScriptFile nicEditUrl [install]]
w <! [("id",id)]
-- | A widget that display the content of an html, But if the user has edition privileges,
-- it permits to edit it in place. So the editor could see the final appearance
-- of what he writes.
--
-- When the user click the save, the content is saved and
-- identified by the key. Then, from now on, all the users will see the saved
-- content instead of the code content.
--
-- The content is saved in a file by default (/texts/ in this versions), but there is
-- a configurable version (`tFieldGen`). The content of the element and the formatting
-- is cached in memory, so the display is, theoretically, very fast.
--
tFieldEd
:: (Functor m, MonadIO m, Executable m,
FormInput v) =>
UserStr -> Key -> v -> View v m ()
tFieldEd muser k text= wfreeze k 0 $ do
content <- liftIO $ readtField text k
nam <- genNewId
let ipanel= nam++"panel"
name= nam++"-"++k
install= "installEditField('"++muser++"','"++cookieuser++"','"++name++"','"++ipanel++"');\n"
getTexts :: (Token -> IO ())
getTexts token = do
let (k,s):_ = tenv token
liftIO $ do
writetField k $ (fromStrNoEncode s `asTypeOf` text)
flushCached k
sendFlush token $ HttpData [] [] ""
return()
requires [JScriptFile nicEditUrl [install]
,JScript ajaxSendText
,JScript installEditField
,ServerProc ("_texts", transient getTexts)]
us <- getCurrentUser
when(us== muser) noCache
(ftag "div" mempty `attrs` [("id",ipanel)]) ++>
notValid (ftag "span" content `attrs` [("id", name)])
installEditField=
"\nfunction installEditField(muser,cookieuser,name,ipanel){\
\if(muser== '' || document.cookie.search(cookieuser+'='+muser) != -1){\
\var myNicEditor = new nicEditor({fullPanel : true, onSave : function(content, id, instance) {\
\ajaxSendText(id,content);\
\myNicEditor.removeInstance(name);\
\myNicEditor.removePanel(ipanel);\
\}});\
\myNicEditor.addInstance(name);\
\myNicEditor.setPanel(ipanel);\
\}};\n"
ajaxSendText = "\nfunction ajaxSendText(id,content){\
\var arr= id.split('-');\
\var k= arr[1];\
\$.ajax({\
\type: 'POST',\
\url: '/_texts',\
\data: k + '='+ encodeURIComponent(content),\
\success: function (resp) {},\
\error: function (xhr, status, error) {\
\var msg = $('<div>' + xhr + '</div>');\
\id1.html(msg);\
\}\
\});\
\return false;\
\};\n"
-- | a text field. Read the cached field value and present it without edition.
tField :: (MonadIO m,Functor m, Executable m, FormInput v)
=> Key
-> View v m ()
tField k = wfreeze k 0 $ do
content <- liftIO $ readtField (fromStrNoEncode "not found") k
notValid content
-- | A multilanguage version of tFieldEd. For a field with @key@ it add a suffix with the
-- two characters of the language used.
mFieldEd muser k content= do
lang <- getLang
tFieldEd muser (k ++ ('-':lang)) content
-- | A multilanguage version of tField
mField k= do
lang <- getLang
tField $ k ++ ('-':lang)
data IteratedId = IteratedId String String deriving (Typeable, Show)
-- | Permits to iterate the presentation of data and//or input fields and widgets within
-- a web page that does not change. The placeholders are created with dField. Both are widget
-- modifiers: The latter gets a widget and create a placeholder in the page that is updated
-- via ajax. The content of the update is the rendering of the widget at each iteration.
-- The former gets a wider widget which contains dField elements and permit the iteration.
-- Whenever a link or a form within the witerate widget is activated, the result is the
-- placeholders filled with the new html content. This content can be data, a input field,
-- a link or a widget. No navigation happens.
--
-- This permits even faster updates than autoRefresh. since the latter refresh the whole
-- widget and it does not permits modifications of the layout at runtime.
--
-- When edTemplate or template is used on top of witerate, the result is editable at runtime,
-- and the span placeholders generated, that are updated via ajax can be relocated within
-- the layout of the template.
--
-- Additionally, contrary to some javascript frameworks, the pages generated with this
-- mechanism are searchable by web crawlers.
witerate
:: (MonadIO m, Functor m, FormInput v) =>
View v m a -> View v m a
witerate w= do
name <- genNewId
setSessionData $ IteratedId name mempty
st <- get
let t= mfkillTime st
let installAutoEval=
"$(document).ready(function(){\
\autoEvalLink('"++name++"',0);\
\autoEvalForm('"++name++"');\
\})\n"
let r = lookup ("auto"++name) $ mfEnv st
w'= w `wcallback` (const $ do
setSessionData $ IteratedId name mempty
modify $ \s -> s{mfPagePath=mfPagePath st
,mfSequence= mfSequence st
,mfHttpHeaders=[]}
w)
ret <- case r of
Nothing -> do
requires [JScript autoEvalLink
,JScript autoEvalForm
,JScript $ timeoutscript t
,JScriptFile jqueryScript [installAutoEval]
,JScript setId]
(ftag "div" <<< w') <! [("id",name)]
Just sind -> refresh $ View $ do
FormElm _ mr <- runView w'
IteratedId _ render <- getSessionData `onNothing` return (IteratedId name mempty)
return $ FormElm (fromStrNoEncode render) mr
-- View $ do
-- let t= mfToken st
-- modify $ \s -> s{mfRequirements=[],mfHttpHeaders=[]} -- !> "just"
-- resetCachePolicy
-- FormElm _ mr <- runView w'
-- setCachePolicy
--
-- reqs <- installAllRequirements
--
-- st' <- get
-- liftIO . sendFlush t $ HttpData
-- (mfHttpHeaders st')
-- (mfCookies st') (toByteString reqs)
-- put st'{mfAutorefresh=True, inSync=True}
-- return $ FormElm mempty Nothing
delSessionData $ IteratedId name mempty
return ret
autoEvalLink = "\nfunction autoEvalLink(id,ind){\
\var id1= $('#'+id);\
\var ida= $('#'+id+' a[class!=\"_noAutoRefresh\"]');\
\ida.off('click');\
\ida.click(function () {\
\if (hadtimeout == true) return true;\
\var pdata = $(this).attr('data-value');\
\var actionurl = $(this).attr('href');\
\var dialogOpts = {\
\type: 'GET',\
\url: actionurl+'?auto'+id+'='+ind,\
\data: pdata,\
\success: function (resp) {\
\eval(resp);\
\autoEvalLink(id,ind);\
\autoEvalForm(id);\
\},\
\error: function (xhr, status, error) {\
\var msg = $('<div>' + xhr + '</div>');\
\id1.html(msg);\
\}\
\};\
\$.ajax(dialogOpts);\
\return false;\
\});\
\}\n"
autoEvalForm = "\nfunction autoEvalForm(id) {\
\var buttons= $('#'+id+' input[type=\"submit\"]');\
\var idform= $('#'+id+' form[class!=\"_noAutoRefresh\"]');\
\buttons.off('click');\
\buttons.click(function(event) {\
\if ($(this).attr('class') != '_noAutoRefresh'){\
\event.preventDefault();\
\if (hadtimeout == true) return true;\
\var $form = $(this).closest('form');\
\var url = $form.attr('action');\
\pdata = 'auto'+id+'=true&'+this.name+'='+this.value+'&'+$form.serialize();\
\postForm(id,url,pdata);\
\return false;\
\}else {\
\noajax= true;\
\return true;\
\}\
\})\
\\n\
\var noajax;\
\idform.submit(function(event) {\
\if(noajax) {noajax=false; return true;}\
\event.preventDefault();\
\var $form = $(this);\
\var url = $form.attr('action');\
\var pdata = 'auto'+id+'=true&' + $form.serialize();\
\postForm(id,url,pdata);\
\return false;})\
\}\
\function postForm(id,url,pdata){\
\var id1= $('#'+id);\
\$.ajax({\
\type: 'POST',\
\url: url,\
\data: 'auto'+id+'=true&'+this.name+'='+this.value+'&'+pdata,\
\success: function (resp) {\
\eval(resp);\
\autoEvalLink(id,0);\
\autoEvalForm(id);\
\},\
\error: function (xhr, status, error) {\
\var msg = $('<div>' + xhr + '</div>');\
\id1.html(msg);\
\}\
\});\
\}"
setId= "function setId(id,v){document.getElementById(id).innerHTML= v;};\n"
-- | Present a widget via AJAX if it is within a 'witerate' context. In the first iteration it present the
-- widget surrounded by a placeholder. subsequent iterations will send just the javascript code
-- necessary for the refreshing of the placeholder.
dField
:: (Monad m, FormInput view) =>
View view m b -> View view m b
dField w= View $ do
id <- genNewId
FormElm render mx <- runView w
st <- get
let env = mfEnv st
IteratedId name scripts <- getSessionData `onNothing` return (IteratedId noid mempty)
let r = lookup ("auto"++name) env
if r == Nothing || (name == noid && newAsk st== True)
then return $ FormElm((ftag "span" render) `attrs` [("id",id)]) mx
else do
setSessionData $ IteratedId name $ scripts <> "setId('"++id++"','" ++ toString (toByteString $ render)++"');"
return $ FormElm mempty mx
noid= "noid"
-- | permits the edition of the rendering of a widget at run time. Once saved, the new rendering
-- becomes the new rendering of the widget for all the users. You must keep the active elements of the
-- template
--
-- the first parameter is the user that has permissions for edition. the second is a key that
-- identifies the template.
edTemplate
:: (MonadIO m, FormInput v, Typeable a) =>
UserStr -> Key -> View v m a -> View v m a
edTemplate muser k w= View $ do
nam <- genNewId
let ipanel= nam++"panel"
name= nam++"-"++k
install= "installEditField('"++muser++"','"++cookieuser++"','"++name++"','"++ipanel++"');\n"
requires [JScript installEditField
,JScriptFile nicEditUrl [install]
,JScript ajaxSendText
,JScriptFile jqueryScript []
,ServerProc ("_texts", transient getTexts)]
us <- getCurrentUser
when(us== muser) noCache
FormElm text mx <- runView w
content <- liftIO $ readtField text k
return $ FormElm (ftag "div" mempty `attrs` [("id",ipanel)] <>
ftag "span" content `attrs` [("id", name)])
mx
where
getTexts :: Token -> IO () -- low level server process
getTexts token= do
let (k,s):_ = tenv token
liftIO $ do
writetField k $ (fromStrNoEncode s `asTypeOf` viewFormat w)
flushCached k
sendFlush token $ HttpData [] [] "" --empty response
return()
viewFormat :: View v m a -> v
viewFormat= undefined -- is a type function
-- | Does the same than template but without the edition facility
template
:: (MonadIO m, FormInput v, Typeable a) =>
Key -> View v m a -> View v m a
template k w= View $ do
FormElm text mx <- runView w
let content= unsafePerformIO $ readtField text k
return $ FormElm content mx
------------------- JQuery widgets -------------------
-- | present the JQuery datePicker calendar to choose a date.
-- The second parameter is the configuration. Use \"()\" by default.
-- See http://jqueryui.com/datepicker/
datePicker :: (Monad m, FormInput v) => String -> Maybe String -> View v m (Int,Int,Int)
datePicker conf jd= do
id <- genNewId
let setit= "$(document).ready(function() {\
\$( '#"++id++"' ).datepicker "++ conf ++";\
\});"
requires
[CSSFile jqueryCSS
,JScriptFile jqueryScript []
,JScriptFile jqueryUI [setit]]
s <- getString jd <! [("id",id)]
let (month,r) = span (/='/') s
let (day,r2)= span(/='/') $ tail r
return (read day,read month, read $ tail r2)
-- | present a jQuery dialog with a widget. When a button is pressed it return the result.
-- The first parameter is the configuration. To make it modal, use \"({modal: true})\" see <http://jqueryui.com/dialog/> for
-- the available configurations.
--
-- The enclosed widget will be wrapped within a form tag if the user do not encloses it using wform.f
wdialog :: (Monad m, FormInput v) => String -> String -> View v m a -> View v m a
wdialog conf title w= do
id <- genNewId
let setit= "$(document).ready(function() {\
\$('#"++id++"').dialog "++ conf ++";\
\var idform= $('#"++id++" form');\
\idform.submit(function(){$(this).dialog(\"close\")})\
\});"
modify $ \st -> st{needForm= HasForm}
requires
[CSSFile jqueryCSS
,JScriptFile jqueryScript []
,JScriptFile jqueryUI [setit]]
(ftag "div" <<< insertForm w) <! [("id",id),("title", title)]
-- | Capture the form or link submissions and send them via jQuery AJAX.
-- The response is the new presentation of the widget, that is updated. No new page is generated
-- but the functionality is equivalent. Only the activated widget rendering is updated
-- in the client, so a widget with autoRefresh can be used in heavyweight pages.
-- If AJAX/JavaScript are not available, the widget is refreshed normally, via a new page.
--
-- autoRefresh encloses the widget in a form tag if it includes form elements.
--
-- If there are more than one autoRefresh, they must be enclosed within 'pageFlow' elements
autoRefresh
:: (MonadIO m,
FormInput v)
=> View v m a
-> View v m a
autoRefresh = update "html"
-- | In some cases, it is necessary that a link or form inside a 'autoRefresh' or 'update' block
-- should not be autorefreshed, since it produces side effects in the rest of the page that
-- affect to the rendering of the whole. If you like to refresh the whole page, simply add
-- noAutoRefresh attribute to the widget to force the refresh of the whole page when it is activated.
--
-- That behavior is common at the last sentence of the 'autoRefresh' block.
--
-- This is a cascade menu example.
--
-- > r <- page $ autoRefresh $ ul <<< do
-- > li <<< wlink OptionA << "option A"
-- > ul <<< li <<< (wlink OptionA1 << "Option A1" <! noAutoRefresh)
-- > <|> li <<< (wlink OptionA2 << "Option A2" <! noAutoRefresh)
-- > <|>...
-- > maybe other content
-- >
-- > case r of
-- > OptionA1 -> pageA1
-- > OptionA2 -> pageA2
--
-- when @option A@ is clicked, the two sub-options appear with autorefresh. Only the two
-- lines are returned by the server using AJAX. but when Option A1-2 is pressed we want to
-- present other pages, so we add the noAutorefresh attribute.
--
-- NOTE: the noAutoRefresh attribute should be added to the <a/> or <form/> tags.
noAutoRefresh= [("class","_noAutoRefresh")]
-- | does the same than `autoRefresh` but append the result of each request to the bottom of the widget
--
-- all the comments and remarks of `autoRefresh` apply here
appendUpdate :: (MonadIO m,
FormInput v)
=> View v m a
-> View v m a
appendUpdate= update "append"
-- | does the same than `autoRefresh` but prepend the result of each request before the current widget content
--
-- all the comments and remarks of `autoRefresh` apply here
prependUpdate :: (MonadIO m,
FormInput v)
=> View v m a
-> View v m a
prependUpdate= update "prepend"
update :: (MonadIO m, FormInput v)
=> String
-> View v m a
-> View v m a
update method w= do
id <- genNewId
st <- get
let t = mfkillTime st -1
installscript=
"$(document).ready(function(){\
\ajaxGetLink('"++id++"');\
\ajaxPostForm('"++id++"');\
\});"
st <- get
let insync = inSync st
let env= mfEnv st
let r= lookup ("auto"++id) env
if r == Nothing
then do
requires [JScript $ timeoutscript t
,JScript ajaxGetLink
,JScript ajaxPostForm
,JScriptFile jqueryScript [installscript]]
(ftag "div" <<< insertForm w) <! [("id",id)]
else refresh $ fromStr (method <> " ") ++> insertForm w
-- View $ do
-- let t= mfToken st -- !> "JUST"