View | Details | Raw Unified | Return to bug 10480
Collapse All | Expand All

(-)a/Koha/FrameworkPlugin.pm (+399 lines)
Line 0 Link Here
1
package Koha::FrameworkPlugin;
2
3
# Copyright 2014 Rijksmuseum
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
=head1 NAME
21
22
Koha::FrameworkPlugin - Facilitate use of plugins in MARC/items editor
23
24
=head1 SYNOPSIS
25
26
    use Koha::FrameworkPlugin;
27
    my $plugin = Koha::FrameworkPlugin({ name => 'EXAMPLE.pl' });
28
    $plugin->build( { id => $id });
29
    $template->param(
30
        javascript => $plugin->javascript,
31
        noclick => $plugin->noclick,
32
    );
33
34
    use Koha::FrameworkPlugin;
35
    my $plugin = Koha::FrameworkPlugin({ name => 'EXAMPLE.pl' });
36
    $plugin->launch( { cgi => $query });
37
38
=head1 DESCRIPTION
39
40
    A framework plugin provides additional functionality to a MARC or item
41
    field. It can be attached to a field in the framework structure.
42
    The functionality is twofold:
43
    - Additional actions on the field via javascript in the editor itself
44
      via events as onfocus, onblur, etc.
45
      Focus may e.g. fill an empty field, Blur or Change may validate.
46
    - Provide an additional form to edit the field value, possibly a
47
      combination of various subvalues. Look at e.g. MARC leader.
48
      The additional form is a popup on top of the MARC/items editor.
49
50
    The plugin code is a perl script (with template for the popup),
51
    essentially doing two things:
52
    1) Build: The plugin returns javascript to the caller (addbiblio.pl a.o.)
53
    2) Launch: The plugin launches the additional form (popup). Launching is
54
       centralized via the plugin_launcher.pl script.
55
56
    This object support two code styles:
57
    - In the new style, the plugin returns a hashref with a builder and a
58
      launcher key pointing to two anynomous subroutines.
59
    - In the old style, the builder is subroutine plugin_javascript and the
60
      launcher is subroutine plugin. For each plugin the routines are
61
      redefined.
62
63
    In cataloguing/value_builder/EXAMPLE.pl, you can find a detailed example
64
    of a new style plugin. As long as we support the old style plugins, the
65
    unit test t/db_dependent/FrameworkPlugin.t still contains an example
66
    of the old style too.
67
68
=head1 METHODS
69
70
=head2 new
71
72
    Create object (via Class::Accessor).
73
74
=head2 build
75
76
    Build uses the builder subroutine of the plugin to build javascript
77
    for the plugin.
78
79
=head2 launch
80
81
    Run the popup of the plugin, as defined by the launcher subroutine.
82
83
=head1 PROPERTIES
84
85
=head2 name
86
87
    Filename of the plugin.
88
89
=head2 path
90
91
    Optional pathname of the plugin.
92
    By default plugins are found in cataloguing/value_builder.
93
94
=head2 errstr
95
96
    Error message.
97
    If set, the plugin will no longer build or launch.
98
99
=head2 javascript
100
101
    Generated javascript for the caller of the plugin (after building).
102
103
=head2 noclick
104
105
    Tells you (after building) that this plugin has no action connected to
106
    to clicking on the buttonDot anchor. (Note that some item plugins
107
    redirect click to focus instead of launching a popup.)
108
109
=head1 ADDITIONAL COMMENTS
110
111
=cut
112
113
use Modern::Perl;
114
115
use base qw(Class::Accessor);
116
117
use C4::Context;
118
use C4::Biblio qw/GetMarcFromKohaField/;
119
120
__PACKAGE__->mk_ro_accessors( qw|
121
    name path errstr javascript noclick
122
|);
123
124
=head2 new
125
126
    Returns new object based on Class::Accessor, loads additional params.
127
    The params hash currently supports keys: name, path, item_style.
128
    Name is mandatory. Path is used in unit testing.
129
    Item_style is used to identify old-style item plugins that still use
130
    an additional (irrelevant) first parameter in the javascript event
131
    functions.
132
133
=cut
134
135
sub new {
136
    my ( $class, $params ) = @_;
137
    my $self = $class->SUPER::new();
138
    if( ref($params) eq 'HASH' ) {
139
        foreach( 'name', 'path', 'item_style' ) {
140
            $self->{$_} = $params->{$_};
141
        }
142
    }
143
    elsif( !ref($params) && $params ) { # use it as plugin name
144
        $self->{name} = $params;
145
        if( $params =~ /^(.*)\/([^\/]+)$/ ) {
146
            $self->{name} = $2;
147
            $self->{path} = $1;
148
        }
149
    }
150
    $self->_error( 'Plugin needs a name' ) if !$self->{name};
151
    return $self;
152
}
153
154
=head2 build
155
156
    Generate html and javascript by calling the builder sub of the plugin.
157
158
    Params is a hashref supporting keys: id (=html id for the input field),
159
    record (MARC record or undef), dbh (database handle), tagslib, tabloop.
160
    Note that some of these parameters are not used in most (if not all)
161
    plugins and may be obsoleted in the future (kept for now to provide
162
    backward compatibility).
163
    The most important one is id; it is used to construct unique javascript
164
    function names.
165
166
    Returns success or failure.
167
168
=cut
169
170
sub build {
171
    my ( $self, $params ) = @_;
172
    return if $self->{errstr};
173
    return 1 if exists $self->{html}; # no rebuild
174
175
    $self->_load if !$self->{_loaded};
176
    return if $self->{errstr}; # load had error
177
    return $self->_generate_js( $params );
178
}
179
180
=head2 launch
181
182
    Launches the popup for this plugin by calling its launcher sub
183
    Old style plugins still expect to receive a CGI oject, new style
184
    plugins expect a params hashref.
185
    Returns undef on failure, otherwise launcher return value (if any).
186
187
=cut
188
189
sub launch {
190
    my ( $self, $params ) = @_;
191
    return if $self->{errstr};
192
193
    $self->_load if !$self->{_loaded};
194
    return if $self->{errstr}; # load had error
195
    return 1 if !exists $self->{launcher}; #just ignore this request
196
    if( defined( &{$self->{launcher}} ) ) {
197
        my $arg= $self->{oldschool}? $params->{cgi}: $params;
198
        return &{$self->{launcher}}( $arg );
199
    }
200
    return $self->_error( 'No launcher sub defined' );
201
}
202
203
# **************  INTERNAL ROUTINES ********************************************
204
205
sub _error {
206
    my ( $self, $info ) = @_;
207
    $self->{errstr} = 'ERROR: Plugin '. ( $self->{name}//'' ). ': '. $info;
208
    return; #always return false
209
}
210
211
sub _load {
212
    my ( $self ) = @_;
213
214
    my ( $rv, $file );
215
    return $self->_error( 'Plugin needs a name' ) if !$self->{name}; #2chk
216
    $self->{path} //= _valuebuilderpath();
217
    $file= $self->{path}. '/'. $self->{name};
218
    return $self->_error( 'File not found' ) if !-e $file;
219
220
    # undefine oldschool subroutines before defining them again
221
    undef &plugin_parameters;
222
    undef &plugin_javascript;
223
    undef &plugin;
224
225
    $rv = do( $file );
226
    return $self->_error( $@ ) if $@;
227
228
    my $type = ref( $rv );
229
    if( $type eq 'HASH' ) { # new style
230
        $self->{oldschool} = 0;
231
        if( exists $rv->{builder} && ref($rv->{builder}) eq 'CODE' ) {
232
            $self->{builder} = $rv->{builder};
233
        } elsif( exists $rv->{builder} ) {
234
            return $self->_error( 'Builder sub is no coderef' );
235
        }
236
        if( exists $rv->{launcher} && ref($rv->{launcher}) eq 'CODE' ) {
237
            $self->{launcher} = $rv->{launcher};
238
        } elsif( exists $rv->{launcher} ) {
239
            return $self->_error( 'Launcher sub is no coderef' );
240
        }
241
    } else { # old school
242
        $self->{oldschool} = 1;
243
        if( defined(&plugin_javascript) ) {
244
            $self->{builder} = \&plugin_javascript;
245
        }
246
        if( defined(&plugin) ) {
247
            $self->{launcher} = \&plugin;
248
        }
249
    }
250
    if( !$self->{builder} && !$self->{launcher} ) {
251
        return $self->_error( 'Plugin does not contain builder nor launcher' );
252
    }
253
    $self->{_loaded} = $self->{oldschool}? 0: 1;
254
        # old style needs reload due to possible sub redefinition
255
    return 1;
256
}
257
258
sub _valuebuilderpath {
259
    return C4::Context->intranetdir . "/cataloguing/value_builder";
260
    #Formerly, intranetdir/cgi-bin was tested first.
261
    #But the intranetdir from koha-conf already includes cgi-bin for
262
    #package installs, single and standard installs.
263
}
264
265
sub _generate_js {
266
    my ( $self, $params ) = @_;
267
268
    my $sub = $self->{builder};
269
    return 1 if !$sub;
270
        #it is safe to assume here that we do have a launcher
271
        #we assume that it is launched in an unorthodox fashion
272
        #just useless to build, but no problem
273
274
    if( !defined(&$sub) ) { # 2chk: if there is something, it should be code
275
        return $self->_error( 'Builder sub not defined' );
276
    }
277
278
    my @params = $self->{oldschool}//0 ?
279
        ( $params->{dbh}, $params->{record}, $params->{tagslib},
280
            $params->{id}, $params->{tabloop} ):
281
        ( $params );
282
    my @rv = &$sub( @params );
283
    return $self->_error( 'Builder sub failed: ' . $@ ) if $@;
284
285
    my $arg= $self->{oldschool}? pop @rv: shift @rv;
286
        #oldschool returns functionname and script; we only use the latter
287
    if( $arg && $arg=~/^\s*\<script/ ) {
288
        $self->_process_javascript( $params, $arg );
289
        return 1; #so far, so good
290
    }
291
    return $self->_error( 'Builder sub returned bad value(s)' );
292
}
293
294
sub _process_javascript {
295
    my ( $self, $params, $script ) = @_;
296
297
    #remove the script tags; we add them again later
298
    $script =~ s/\<script[^>]*\>\s*(\/\/\<!\[CDATA\[)?\s*//s;
299
    $script =~ s/(\/\/\]\]\>\s*)?\<\/script\>//s;
300
301
    my $id = $params->{id}//'';
302
    my $bind = '';
303
    my $clickfound = 0;
304
    my @events = qw|click focus blur change mouseover mouseout mousedown
305
        mouseup mousemove keydown keypress keyup|;
306
    foreach my $ev ( @events ) {
307
        my $scan = $ev eq 'click' && $self->{oldschool}? 'clic': $ev;
308
        if( $script =~ /function\s+($scan\w+)\s*\(([^\)]*)\)/is ) {
309
            my ( $bl, $sl ) = $self->_add_binding( $1, $2, $ev, $id );
310
            $script .= $sl;
311
            $bind .= $bl;
312
            $clickfound = 1 if $ev eq 'click';
313
        }
314
    }
315
    if( !$clickfound ) { # make buttonDot do nothing
316
        my ( $bl ) = $self->_add_binding( 'noclick', '', 'click', $id );
317
        $bind .= $bl;
318
    }
319
    $self->{noclick} = !$clickfound;
320
    $self->{javascript}= _merge_script( $id, $script, $bind );
321
}
322
323
sub _add_binding {
324
# adds some jQuery code for event binding:
325
# $bind contains lines for the actual event binding: .click, .focus, etc.
326
# $script contains function definitions (if needed)
327
    my ( $self, $fname, $pars, $ev, $id ) = @_;
328
    my ( $bind, $script );
329
    my $ctl= $ev eq 'click'? 'buttonDot_'.$id: $id;
330
        #click event applies to buttonDot
331
332
    if( $pars =~ /^(e|ev|event)$/i ) { # new style event handler assumed
333
        $bind= qq|    \$("#$ctl").$ev(\{id: '$id'\}, $fname);\n|;
334
        $script='';
335
    } elsif( $fname eq 'noclick' ) { # no click: return false, no scroll
336
        $bind= qq|    \$("#$ctl").$ev(function () { return false; });\n|;
337
        $script='';
338
    } else { # add real event handler calling the function found
339
        $bind=qq|    \$("#$ctl").$ev(\{id: '$id'\}, ${fname}_handler);\n|;
340
        $script = $self->_add_handler( $ev, $fname );
341
    }
342
    return ( $bind, $script );
343
}
344
345
sub _add_handler {
346
# adds a handler with event parameter
347
# event.data.id is passed to the plugin function in parameters
348
# for the click event we always return false to prevent scrolling
349
    my ( $self, $ev, $fname ) = @_;
350
    my $first= $self->_first_item_par( $ev );
351
    my $prefix= $ev eq 'click'? '': 'return ';
352
    my $suffix= $ev eq 'click'? "\n    return false;": '';
353
    return <<HERE;
354
function ${fname}_handler(event) {
355
    $prefix$fname(${first}event.data.id);$suffix
356
}
357
HERE
358
}
359
360
sub _first_item_par {
361
    my ( $self, $event ) = @_;
362
    # needed for backward compatibility
363
    # js event functions in old style item plugins have an extra parameter
364
    # BUT.. not for all events (exceptions provide employment :)
365
    if( $self->{item_style} && $self->{oldschool} &&
366
            $event=~/focus|blur|change/ ) {
367
        return qq/'0',/;
368
    }
369
    return '';
370
}
371
372
sub _merge_script {
373
# Combine script and event bindings, enclosed in script tags.
374
# The BindEvents function is added to easily repeat event binding;
375
# this is used in additem.js for dynamically created item blocks.
376
    my ( $id, $script, $bind ) = @_;
377
    chomp ($script, $bind);
378
    return <<HERE;
379
<script type="text/javascript">
380
//<![CDATA[
381
$script
382
function BindEvents$id() {
383
$bind
384
}
385
\$(document).ready(function() {
386
    BindEvents$id();
387
});
388
//]]>
389
</script>
390
HERE
391
}
392
393
=head1 AUTHOR
394
395
    Marcel de Rooy, Rijksmuseum Amsterdam, The Netherlands
396
397
=cut
398
399
1;
(-)a/t/db_dependent/FrameworkPlugin.t (-1 / +278 lines)
Line 0 Link Here
0
- 
1
use Modern::Perl;
2
3
use C4::Auth;
4
use C4::Output;
5
use Koha::FrameworkPlugin;
6
7
use CGI;
8
use File::Temp qw/tempfile/;
9
use Getopt::Long;
10
use Test::MockModule;
11
use Test::More tests => 5;
12
13
my @includes;
14
GetOptions( 'include=s{,}' => \@includes ); #not used by default !
15
16
my $dbh = C4::Context->dbh;
17
$dbh->{AutoCommit} = 0;
18
$dbh->{RaiseError} = 1;
19
20
subtest 'Test01 -- Simple tests for new and name' => sub {
21
    plan tests => 7;
22
    test01();
23
    $dbh->rollback;
24
};
25
subtest 'Test02 -- test build with old styler and marc21_leader' => sub {
26
    plan tests => 5;
27
    test02();
28
    $dbh->rollback;
29
};
30
subtest 'Test03 -- tests with bad plugins' => sub {
31
    test03();
32
    $dbh->rollback;
33
};
34
subtest 'Test04 -- tests with new style plugin' => sub {
35
    plan tests => 5;
36
    test04();
37
    $dbh->rollback;
38
};
39
subtest 'Test05 -- tests with build and launch for default plugins' => sub {
40
    test05( \@includes );
41
    $dbh->rollback;
42
};
43
$dbh->rollback;
44
45
sub test01 {
46
    #empty plugin
47
    my $plugin= Koha::FrameworkPlugin->new;
48
    is( ref($plugin), 'Koha::FrameworkPlugin', 'Got an object' );
49
    isnt( $plugin->errstr, undef, 'We should have an error for missing name');
50
    is( $plugin->build, undef, 'Build returns undef');
51
52
    #tests for name and path, with/without hashref
53
    $plugin= Koha::FrameworkPlugin->new( { name => 'marc21_leader.pl' } );
54
    is( $plugin->name, 'marc21_leader.pl', 'Check name without path in hash' );
55
    $plugin= Koha::FrameworkPlugin->new( 'marc21_leader.pl' );
56
    is( $plugin->name, 'marc21_leader.pl', 'Check name without path' );
57
    $plugin= Koha::FrameworkPlugin->new( 'cataloguing/value_builder/marc21_leader.pl' );
58
    is( $plugin->name, 'marc21_leader.pl', 'Check name with path' );
59
    $plugin= Koha::FrameworkPlugin->new({ path => 'cataloguing/value_builder', name => 'marc21_leader.pl' });
60
    is( $plugin->name, 'marc21_leader.pl', 'Check name and path in hash' );
61
}
62
63
sub test02 {
64
    # first test an old style item plugin
65
    my $old = old01(); # plugin filename
66
    my $path;
67
    if( $old =~ /^(.*)\/([^\/]+)$/ ) { # extract path
68
        $path = $1;
69
        $old = $2;
70
    }
71
    my $plugin= Koha::FrameworkPlugin->new({
72
        name => $old, path => $path, item_style => 1,
73
    });
74
    my $pars= { id => '234567' };
75
    is( $plugin->build($pars), 1, 'Build oldstyler successful' );
76
    is( length($plugin->javascript)>0 && !$plugin->noclick, 1,
77
        'Checked javascript and noclick' );
78
79
    # now test marc21_leader
80
    $plugin= Koha::FrameworkPlugin->new( { name => 'marc21_leader.pl' } );
81
    $pars= { dbh => $dbh, id => '123456' };
82
    is( $plugin->build($pars), 1, 'Build marc21_leader successful' );
83
    is( $plugin->javascript =~ /<script.*function.*\<\/script\>/s, 1,
84
        'Javascript looks ok' );
85
    is( $plugin->noclick, '', 'marc21_leader should have a popup');
86
}
87
88
sub test03 {
89
    #file not found
90
    my $plugin= Koha::FrameworkPlugin->new('file_does_not_exist');
91
    $plugin->build;
92
    is( $plugin->errstr =~ /not found/i, 1, 'File not found-message');
93
94
    #three bad ones: no perl, syntax error, bad return value
95
    foreach my $f ( bad01(), bad02(), bad03() ) {
96
        next if !$f;
97
        $plugin= Koha::FrameworkPlugin->new( $f );
98
        $plugin->build({ id => '998877' });
99
        is( defined($plugin->errstr), 1,
100
            "Saw: ". ( $plugin->errstr//'no error??' ));
101
    }
102
    done_testing();
103
}
104
105
sub test04 {
106
    #two simple new style plugins
107
    my $plugin= Koha::FrameworkPlugin->new( good01() );
108
    my $pars= { id => 'example_345' };
109
    is( $plugin->build($pars), 1, 'Build 1 ok');
110
    isnt( $plugin->javascript, '', 'Checked javascript property' );
111
112
    $plugin= Koha::FrameworkPlugin->new( ugly01() );
113
    $pars= { id => 'example_456' };
114
    is( $plugin->build($pars), 1, 'Build 2 ok');
115
    is( $plugin->build($pars), 1, 'Second build 2 ok');
116
    is( $plugin->launch($pars), 'abc', 'Launcher returned something' );
117
        #note: normally you will not call build and launch like that
118
}
119
120
sub test05 {
121
    my ( $incl ) = @_;
122
    #mock to simulate some authorization and eliminate lots of output
123
    my $launched = 0;
124
    my $mContext = new Test::MockModule('C4::Context');
125
    my $mAuth = new Test::MockModule('C4::Auth');
126
    my $mOutput = new Test::MockModule('C4::Output');
127
    $mContext->mock( 'userenv', \&mock_userenv );
128
    $mAuth->mock( 'checkauth', sub { return ( 1, undef, 1, all_perms() ); } );
129
    $mOutput->mock('output_html_with_http_headers',  sub { ++$launched; } );
130
131
    my $cgi=new CGI;
132
    my ( $plugins, $min ) = selected_plugins( $incl );
133
134
    # test building them
135
    my $objs;
136
    foreach my $f ( @$plugins ) {
137
        $objs->{$f} = Koha::FrameworkPlugin->new( $f );
138
        my $pars= { dbh => $dbh, id => $f };
139
        is( $objs->{$f}->build($pars), 1, "Builded ".$objs->{$f}->name );
140
    }
141
142
    # test launching them (but we cannot verify returned results here)
143
    undef $objs;
144
    foreach my $f ( @$plugins ) {
145
        $objs->{$f} = Koha::FrameworkPlugin->new( $f );
146
        my $pars= { dbh => $dbh, id => $f };
147
        $objs->{$f}->launch({ cgi => $cgi });
148
            # may generate some uninitialized warnings for missing params
149
        is( $objs->{$f}->errstr, undef, "Launched ".$objs->{$f}->name );
150
    }
151
    is( $launched >= $min, 1,
152
            "$launched of ". scalar @$plugins.' plugins generated output ');
153
    done_testing();
154
}
155
156
sub selected_plugins {
157
    my ( $incl ) = @_;
158
    #if you use includes, FIRST assure yourself that you do not
159
    #include any destructive perl scripts! You know what you are doing..
160
161
    my ( @fi, $min);
162
    if( $incl && @$incl ) {
163
        @fi = @$incl;
164
        $min = 0; #not sure how many will output
165
    } else { # some default MARC, UNIMARC and item plugins
166
        @fi = qw| barcode.pl dateaccessioned.pl marc21_field_003.pl
167
marc21_field_005.pl marc21_field_006.pl marc21_field_007.pl marc21_field_008.pl
168
marc21_field_008_authorities.pl marc21_leader.pl marc21_leader_authorities.pl
169
unimarc_leader.pl unimarc_field_100.pl unimarc_field_105.pl
170
unimarc_field_106.pl unimarc_field_110.pl unimarc_field_120.pl
171
unimarc_field_130.pl unimarc_field_140.pl unimarc_field_225a.pl
172
unimarc_field_4XX.pl |;
173
        $min = 16; # the first four generate no output
174
    }
175
    @fi = grep
176
        { !/ajax|callnumber(-KU)?\.pl|labs_theses/ } # skip these
177
        @fi;
178
    return ( \@fi, $min);
179
}
180
181
sub mock_userenv {
182
    return { branch => 'CPL', flags => 1, id => 1 };
183
}
184
185
sub all_perms {
186
    my $p = $dbh->selectcol_arrayref("SELECT flag FROM userflags");
187
    my $rv= {};
188
    foreach my $module ( @$p ) {
189
        $rv->{ $module } = 1;
190
    }
191
    return $rv;
192
}
193
194
sub mytempfile {
195
    my ( $fh, $fn ) = tempfile( SUFFIX => '.plugin', UNLINK => 1 );
196
    print $fh $_[0]//'';
197
    close $fh;
198
    return $fn;
199
}
200
201
sub old01 {
202
# simple old style item plugin: note that Focus has two pars
203
# includes a typical empty Clic function and plugin subroutine
204
    return mytempfile( <<'HERE'
205
sub plugin_javascript {
206
    my ($dbh,$record,$tagslib,$field_number,$tabloop) = @_;
207
    my $function_name = $field_number;
208
    my $res = "
209
<script type=\"text/javascript\">
210
//<![CDATA[
211
function Focus$function_name(subfield_managed,id) {
212
    document.getElementById(id).value='test';
213
    return 0;
214
}
215
function Clic$function_name(subfield_managed) {
216
}
217
//]]>
218
</script>
219
";
220
    return ($function_name,$res);
221
}
222
sub plugin {
223
    return "";
224
}
225
HERE
226
    );
227
}
228
229
sub good01 { #very simple new style plugin, no launcher
230
    return mytempfile( <<'HERE'
231
my $builder = sub {
232
    my $params = shift;
233
    return qq|
234
<script type="text/javascript">
235
    function Focus$params->{id}(event) {
236
        if( document.getElementById(event.data.id).value == '' ) {
237
            document.getElementById(event.data.id).value='EXAMPLE: ';
238
        }
239
    }
240
</script>|;
241
};
242
return { builder => $builder };
243
HERE
244
    );
245
}
246
247
sub bad01 { # this is no plugin
248
    return mytempfile( 'Just nonsense' );
249
}
250
251
sub bad02 { # common syntax error: you forgot the semicolon of sub1 declare
252
    return mytempfile( <<'HERE'
253
my $sub1= sub {
254
    my $params = shift;
255
    return qq|<script type="text/javascript">function Change$params->{id}(event) { alert("Changed"); }</script>|;
256
}
257
return { builder => $sub1 };
258
HERE
259
    );
260
}
261
262
sub bad03 { # badscript tag should trigger an error
263
    return mytempfile( <<'HERE'
264
my $sub1= sub {
265
    my $params = shift;
266
    return qq|<badscript type="text/javascript">function Click$params->{id} (event) { alert("Hi there"); return false; }</badscript>|;
267
};
268
return { builder => $sub1 };
269
HERE
270
    );
271
}
272
273
sub ugly01 { #works, but not very readable..
274
    return mytempfile( <<'HERE'
275
return {builder=>sub{return qq|<script type="text/javascript">function Blur$_[0]->{id}(event){alert('Bye');}</script>|;},launcher=>sub{'abc'}};
276
HERE
277
    );
278
}

Return to bug 10480