1
- /* global jQuery, twentyc */
2
1
( function ( twentyc , $ ) {
3
2
4
3
/**
20
19
}
21
20
} ) ( jQuery ) ;
22
21
22
+ /**
23
+ * takes a jquery result and replaces all plain text occurences
24
+ * of email addresses and urls with links
25
+ *
26
+ * @method replace_urls_with_links
27
+ */
28
+
29
+ function replace_urls_with_links ( jQueryResult ) {
30
+ jQueryResult . contents ( ) . filter ( function ( ) {
31
+ return this . nodeName != "A" ;
32
+ } ) . each ( function ( ) {
33
+ const text = $ ( this ) . text ( ) ;
34
+ const urlRegex = / ( ( h t t p s ? : \/ \/ [ ^ \s ] + ) | ( \S + @ \S + ) ) / g;
35
+ const html = text . replace ( urlRegex , ( match ) => {
36
+ const emailRegex = / \S + @ \S + / ;
37
+ if ( emailRegex . test ( match ) ) {
38
+ return `<a href="mailto:${ match } ">${ match } </a>` ;
39
+ }
40
+ return `<a href="${ match } ">${ match } </a>` ;
41
+ } ) ;
42
+ $ ( this ) . replaceWith ( html ) ;
43
+ } ) ;
44
+ }
45
+
23
46
/**
24
47
* namespace for twentyc.rest
25
48
*/
@@ -46,9 +69,77 @@ twentyc.rest = {
46
69
*/
47
70
48
71
csrf : ""
72
+ } ,
73
+
74
+ /**
75
+ * object holding URL utility functions
76
+ * @property url
77
+ * @type Object
78
+ * @namespace twentyc.rest
79
+ */
80
+
81
+ url : {
82
+ /**
83
+ * Trims leading and trailing slashes from the given endpoint.
84
+ *
85
+ * @method trim_endpoint
86
+ * @param {String } endpoint - The endpoint string to trim.
87
+ * @return {String } The trimmed endpoint string.
88
+ */
89
+ trim_endpoint : function ( endpoint ) {
90
+ // urljoin is not guaranteed to strip trailing double slashes on
91
+ // either side of the endpoint, so we do it manually
92
+ return endpoint . replace ( / ^ \/ + | \/ + $ / g, "" ) ;
93
+ } ,
94
+
95
+ /**
96
+ * Joins URL parts, removing extra slashes at the edges of the parts.
97
+ *
98
+ * @method url_join
99
+ * @param {String } left - The leftmost URL part.
100
+ * @param {...String } args - The remaining URL parts.
101
+ * @return {String } The joined URL string with removed extra slashes.
102
+ */
103
+ url_join : function ( left , ...args ) {
104
+ // Simplified urljoin that gets rid of extra / at the edges
105
+ // of parts
106
+
107
+ let right = [ ] ;
108
+ let trailing_slash = ! twentyc . rest . no_end_slash ;
109
+
110
+ // trim left
111
+
112
+ left = left . replace ( / \/ + $ / g, "" ) ;
113
+
114
+ for ( const parts of args ) {
115
+ right = right . concat (
116
+ parts
117
+ . split ( "/" )
118
+ . filter ( ( part ) => part )
119
+ . map ( ( part ) => this . trim_endpoint ( part ) )
120
+ ) ;
121
+ }
122
+
123
+ if ( ! right . length )
124
+ return trailing_slash ? `${ left } /` : left ;
125
+
126
+ right = right . join ( "/" ) ;
127
+
128
+ if ( ! left ) {
129
+ return trailing_slash ? `/${ right } /` : `/${ right } ` ;
130
+ }
131
+
132
+ const joinedUrl = `${ left . replace ( / \/ $ / , "" ) } /${ right } ` ;
133
+
134
+ return trailing_slash ? `${ joinedUrl } /` : joinedUrl ;
135
+ }
136
+
49
137
}
138
+
50
139
} ;
51
140
141
+
142
+
52
143
/**
53
144
* Wrapper for API responses
54
145
* @class Response
@@ -392,12 +483,10 @@ twentyc.rest.Client = twentyc.cls.define(
392
483
*/
393
484
394
485
endpoint_url : function ( endpoint ) {
395
-
396
- var end_slash = ( twentyc . rest . no_end_slash ? "" : "/" ) ;
397
-
398
486
if ( ! endpoint )
399
- return this . base_url + end_slash ;
400
- return this . base_url + "/" + endpoint + end_slash ;
487
+ return twentyc . rest . url . url_join ( this . base_url ) ;
488
+
489
+ return twentyc . rest . url . url_join ( this . base_url , endpoint ) ;
401
490
} ,
402
491
403
492
/**
@@ -468,16 +557,18 @@ twentyc.rest.Client = twentyc.cls.define(
468
557
* @returns {Promise }
469
558
*/
470
559
471
- write : function ( endpoint , data , method ) {
560
+ write : function ( endpoint , data , method , url = null ) {
561
+ url = url || this . endpoint_url ( endpoint ) ;
472
562
method = method . toLowerCase ( ) ;
563
+
473
564
$ ( this ) . trigger ( "api-request:before" , [ endpoint , data , method ] )
474
565
$ ( this ) . trigger ( "api-write:before" , [ endpoint , data , method ] )
475
566
$ ( this ) . trigger ( "api-" + method + ":before" , [ endpoint , data ] )
476
567
var request = new Promise ( function ( resolve , reject ) {
477
568
$ . ajax ( {
478
569
dataType : "json" ,
479
570
method : method . toUpperCase ( ) ,
480
- url : this . format_request_url ( this . endpoint_url ( endpoint ) , method ) ,
571
+ url : this . format_request_url ( url , method ) ,
481
572
data : this . encode ( data ) ,
482
573
headers : {
483
574
"Content-Type" : "application/json" ,
@@ -761,9 +852,12 @@ twentyc.rest.Widget = twentyc.cls.extend(
761
852
for ( i = 0 ; i < errors . length ; i ++ ) {
762
853
$ ( twentyc . rest ) . trigger ( "non-field-error" , [ errors [ i ] , errors , i , error_node , this ] ) ;
763
854
if ( errors [ i ] )
764
- error_node . append ( $ ( '<div>' ) . addClass ( "non-field-error" ) . text ( errors [ i ] ) )
855
+ error_node . append ( $ ( '<div>' ) . addClass ( "non-field-error" ) . text ( errors [ i ] ) ) ;
765
856
}
766
- this . element . prepend ( error_node )
857
+
858
+ replace_urls_with_links ( error_node ) ;
859
+
860
+ this . element . prepend ( error_node ) ;
767
861
} ,
768
862
769
863
/**
@@ -794,8 +888,17 @@ twentyc.rest.Widget = twentyc.cls.extend(
794
888
data [ input . attr ( "name" ) ] = input . val ( ) ;
795
889
}
796
890
}
891
+
892
+ // treat blank values as null where necessary
893
+
894
+ if ( input . data ( "blank-as-null" ) == "yes" && input . val ( ) == "" ) {
895
+ data [ input . attr ( "name" ) ] = null ;
896
+ }
797
897
} ) ;
798
898
} ) ;
899
+
900
+ $ ( this ) . trigger ( "payload:after" , [ data ] ) ;
901
+
799
902
return data ;
800
903
801
904
} ,
@@ -821,7 +924,7 @@ twentyc.rest.Widget = twentyc.cls.extend(
821
924
822
925
var k , tag , val , formatter ;
823
926
for ( k in data ) {
824
- formatter = this . formatters [ k ] ;
927
+ var formatter = this . formatters [ k ] ;
825
928
var col_element = element . find ( '[data-field="' + k + '"]' ) ;
826
929
827
930
if ( col_element . length )
@@ -1173,6 +1276,59 @@ twentyc.rest.Input = twentyc.cls.extend(
1173
1276
twentyc . rest . Widget
1174
1277
) ;
1175
1278
1279
+ /**
1280
+ * Checkbox widget
1281
+ *
1282
+ * Wires a html element (input type="checkbox") to the API
1283
+ *
1284
+ * The select element should have the following attributes set
1285
+ *
1286
+ * # required
1287
+ *
1288
+ * - data-api-base: api root or full path to endpoint
1289
+ *
1290
+ * @class Button
1291
+ * @extends twentyc.rest.Input
1292
+ * @namespace twentyc.rest
1293
+ * @param {jQuery result } jq jquery result holding the button element
1294
+ */
1295
+
1296
+
1297
+ twentyc . rest . Checkbox = twentyc . cls . extend (
1298
+ "Checkbox" ,
1299
+ {
1300
+ payload : function ( ) {
1301
+ var pl = { } ;
1302
+ pl [ this . element . attr ( 'name' ) ] = ( this . element . prop ( "checked" ) ? true : false ) ;
1303
+ return pl ;
1304
+ } ,
1305
+ bind : function ( jq ) {
1306
+ this . Widget_bind ( jq ) ;
1307
+ this . method = jq . data ( "api-method" ) || "POST" ;
1308
+
1309
+ this . element . on ( "change" , function ( ev ) {
1310
+
1311
+ var confirm_required = this . element . data ( "confirm" ) ;
1312
+ if ( confirm_required && ! confirm ( confirm_required ) )
1313
+ return ;
1314
+
1315
+ this . clear_errors ( ) ;
1316
+
1317
+ var action = this . action ;
1318
+ var fn = this [ this . method . toLowerCase ( ) ] . bind ( this ) ;
1319
+
1320
+ fn ( action , this . payload ( ) ) . then (
1321
+ this . post_success . bind ( this ) ,
1322
+ this . post_failure . bind ( this )
1323
+ ) ;
1324
+ } . bind ( this ) ) ;
1325
+
1326
+ }
1327
+ } ,
1328
+ twentyc . rest . Input
1329
+ ) ;
1330
+
1331
+
1176
1332
/**
1177
1333
* Button widget
1178
1334
*
@@ -1315,6 +1471,7 @@ twentyc.rest.Select = twentyc.cls.extend(
1315
1471
$ ( this . proxy_data ) . find ( 'option' ) . each ( function ( ) {
1316
1472
select . append ( $ ( this ) . clone ( ) ) ;
1317
1473
} ) ;
1474
+ $ ( this ) . trigger ( "load:after" , [ select , { } , this ] ) ;
1318
1475
return new Promise ( ( resolve , reject ) => {
1319
1476
resolve ( ) ;
1320
1477
} ) ;
@@ -1361,6 +1518,8 @@ twentyc.rest.Select = twentyc.cls.extend(
1361
1518
var selected_field = this . selected_field
1362
1519
var widget = this ;
1363
1520
1521
+ var old_val = select . val ( ) ;
1522
+
1364
1523
select . empty ( ) ;
1365
1524
1366
1525
if ( this . null_option ) {
@@ -1379,8 +1538,11 @@ twentyc.rest.Select = twentyc.cls.extend(
1379
1538
select . append ( opt ) ;
1380
1539
} ) ;
1381
1540
1382
- if ( select_this )
1541
+ if ( select_this ) {
1383
1542
select . val ( select_this ) ;
1543
+ if ( select_this != old_val )
1544
+ select . trigger ( "change" , [ ] ) ;
1545
+ }
1384
1546
1385
1547
$ ( this ) . trigger ( "load:after" , [ select , response . content . data , this ] ) ;
1386
1548
} . bind ( this ) ) ;
@@ -1587,7 +1749,7 @@ twentyc.rest.List = twentyc.cls.extend(
1587
1749
response . rows ( function ( row , idx ) {
1588
1750
this . insert ( row ) ;
1589
1751
} . bind ( this ) ) ;
1590
- $ ( this ) . trigger ( "load:after" ) ;
1752
+ $ ( this ) . trigger ( "load:after" , [ response ] ) ;
1591
1753
return
1592
1754
} . bind ( this ) ) ;
1593
1755
} ,
@@ -1733,6 +1895,7 @@ twentyc.rest.List = twentyc.cls.extend(
1733
1895
var method = ( $ ( this ) . data ( "api-method" ) || "POST" ) . toLowerCase ( ) ;
1734
1896
var action = $ ( this ) . data ( "api-action" ) . toLowerCase ( ) ;
1735
1897
var callback = $ ( this ) . data ( "api-callback" ) ;
1898
+ var callback_name = $ ( this ) . data ( "api-callback" ) ;
1736
1899
var confirm_set = $ ( this ) . data ( "confirm" )
1737
1900
1738
1901
if ( callback )
@@ -1743,13 +1906,15 @@ twentyc.rest.List = twentyc.cls.extend(
1743
1906
return ;
1744
1907
var apiobj = row . data ( "apiobject" )
1745
1908
var _action = action . replace (
1746
- / \{ ( [ ^ { } ] + ) \} / ,
1909
+ / \{ ( [ ^ \{ \ }] + ) \} / ,
1747
1910
( match , p1 , offset , string ) => {
1748
1911
return apiobj [ p1 ] ;
1749
1912
}
1750
1913
)
1751
1914
widget [ method ] ( _action , row . data ( "apiobject" ) ) . then (
1752
1915
callback , widget . action_failure . bind ( widget )
1916
+ ) . then (
1917
+ $ ( widget ) . trigger ( "api_callback_" + callback_name + ":after" )
1753
1918
) ;
1754
1919
} ) ;
1755
1920
} ) ;
@@ -1801,9 +1966,7 @@ twentyc.rest.List = twentyc.cls.extend(
1801
1966
Specific to django-rest-framework: we add "ordering" as a query
1802
1967
parameter to the API calls
1803
1968
*/
1804
- this . payload = function ( ) { return { ordering : this . ordering } }
1805
-
1806
- console . log ( "sort init" , this ) ;
1969
+ this . payload = function ( ) { return { ordering : this . ordering } ; } ;
1807
1970
} ,
1808
1971
1809
1972
sort : function ( target , secondary ) {
@@ -1813,7 +1976,7 @@ twentyc.rest.List = twentyc.cls.extend(
1813
1976
} else {
1814
1977
this . sort_target = target ;
1815
1978
this . sort_asc = true ;
1816
- }
1979
+ } ;
1817
1980
this . load ( ) ;
1818
1981
} ,
1819
1982
@@ -1901,12 +2064,11 @@ twentyc.rest.PermissionsForm = twentyc.cls.extend(
1901
2064
1902
2065
set_flag_values : function ( flags ) {
1903
2066
this . element . find ( 'input[data-permission-flag]' ) . each ( function ( ) {
1904
- var value , flag_name = $ ( this ) . data ( "permission-flag" )
2067
+ var flag_name = $ ( this ) . data ( "permission-flag" )
1905
2068
if ( flag_name . length == 1 ) {
1906
- value = ( flags . perms . indexOf ( flag_name ) > - 1 )
2069
+ var value = ( flags . perms . indexOf ( flag_name ) > - 1 )
1907
2070
} else {
1908
- var i ;
1909
- value = true ;
2071
+ var i , value = true ;
1910
2072
for ( i = 0 ; i < flag_name . length ; i ++ ) {
1911
2073
if ( flags . perms . indexOf ( flag_name . charAt ( i ) ) == - 1 )
1912
2074
value = false ;
0 commit comments