Group
Extension

Mojolicious-Plugin-DevexpressHelpers/lib/Mojolicious/Plugin/DevexpressHelpers/Helpers.pm

use utf8;
package Mojolicious::Plugin::DevexpressHelpers::Helpers;
$Mojolicious::Plugin::DevexpressHelpers::Helpers::VERSION = '0.163572';
#ABSTRACT: Helpers for Devexpress controls are defined here
use Modern::Perl;
use Mojo::ByteStream;
use MojoX::AlmostJSON qw(encode_json);
use constant DEBUG => 0;

#Not sure why C<out> function have to decode from utf8,
#but it make my day!
our $OUT_DECODE = 'UTF-8';
our $INDENT_BINDING = 0;

my @generic_controls = qw(
		Accordion
		ActionSheet
		Autocomplete
		Box
		CheckBox
		Calendar
		ColorBox
		ContextMenu
		DateBox
		DeferRendering
		FileUploader
		Gallery
		List
		LoadIndicator
		Lookup
		Map
		MultiView
		NavBar
		NumberBox
		Panorama
		Pivot
		PivotGrid
		PivotGridFieldChooser
		Popover
		ProgressBar
		RadioGroup
		RangeSlider
		Resizable
		ResponsiveBox
		Scheduler
		ScrollView
		SelectBox
		Slider
		Switch
		TabPanel
		Tabs
		TagBox
		TextArea
		TextBox
		TitleView
		Toast
		Toolbar
		TreeView
	);

#Helper method to export without prepending a prefix
my @without_prefix = qw( dxbuild required_assets require_asset indent_binding append_js prepend_js );

#Helper method to export with prepending a prefix
my @with_prefix = (qw( Button DataGrid Form Popup Menu LoadPanel Lookup ),
				   @generic_controls);


sub out{
	my $tag = shift;
	my $bytes = Mojo::ByteStream->new($tag);
	return $bytes->decode($OUT_DECODE) if defined $OUT_DECODE;
	return $bytes;
}

sub new{
	my $class = shift;
	my $self = bless { 
			next_id => 1,
			bindings => '',
			before_bindings => '',
			after_bindings => '',
		}, $class;
	return $self;
}

sub indent_binding{
	my $self = shift;
	$INDENT_BINDING = shift;
}

sub add_binding{
	my $self = shift;
	$self->{bindings} .= join "\n", @_;
}

sub next_id{
	my $self = shift;
	return "dxctl".($self->{next_id}++);
}

sub new_id{
	my ($c, $attrs) = @_;
	#should compute a new uniq id 
	$c->stash('dxHelper')->next_id;
}

sub dxbind{
	my ($c, $control, $id, $attrs, $extensions, $befores, $afters) = @_;
	#should return html code to be associated to the control
	$befores //=[];
	$afters  //=[];
	#http://stackoverflow.com/questions/9930577/jquery-dot-in-id-selector
	my $jquery_id = $id;
	$jquery_id =~ s{\.}{\\\\.}g;
	my $prepend = ref $attrs eq 'HASH' && delete $attrs->{prependTo};
	my $append  = ref $attrs eq 'HASH' && delete $attrs->{appendTo};
	my $binding = '';
	if($prepend || $append){
		$binding = '$(\'<div id="'.$id.'">\').'.$control.'(';
	}
	else{
		$binding = '$("#'.$jquery_id.'").'.$control.'(';
	}
    my @options;

	if (ref($attrs) eq 'HASH') {
		$binding .= '{';
		$binding .= "\n  " if $INDENT_BINDING;
		for my $k ( sort keys %$attrs){
			my $v = $attrs->{$k} // '';
			if(ref($v) eq 'SCALAR'){
				#unref protected scalar
				$v = $$v;
			}
			elsif ($v!~/^\s*(?:function\s*\()/) {
				$v =  encode_json $v;
			}
			push @options, "$k: $v";
		}
	}
	else{
		push @options, $attrs;
	}
    $binding .= join ",\n".($INDENT_BINDING?'  ':''), @options;
	$binding .= '}' if ref($attrs) eq 'HASH';
    $binding .= ')';
	$binding .= '.prependTo("'.$prepend.'")' if $prepend;
	$binding .= '.appendTo("'.$append.'")'   if $append;
	$binding .= ';' . ($INDENT_BINDING?"\n":"");
	#append some extensions (eg: dxdatagrid)
	$binding .= join ";\n".($INDENT_BINDING?'  ':''), @$extensions if defined $extensions;
	$c->stash('dxHelper')->add_binding($binding);
	my $html_code = "<div id=\"$id\"></div>";
	if($prepend || $append){
		$html_code = '';
	}
	out join('',@$befores, $html_code ,@$afters);
}


sub parse_attributs{
	my $c = shift;
	my @implicit_args = @{shift()};
	my %attrs;
	IMPLICIT_ARGUMENT:
	while(@_ and @implicit_args){
		my $ref = ref($_[0]);
		my $implicit = shift @implicit_args || '';
		last unless $ref =~ /^(?:|SCALAR)$/
		or (substr($implicit,0,1) eq '@' and $ref eq 'ARRAY')
		or (substr($implicit,0,1) eq '%' and $ref eq 'HASH')
		or (substr($implicit,0,1) eq '\\' and $ref eq 'REF')
		or (substr($implicit,0,1) eq '*');
		$implicit =~ s/^[\\\*\%\@]//;
		$attrs{ $implicit } = shift @_;
	}
	if(my $args = shift){
		if(ref($args) eq 'HASH'){
			NAMED_ARGUMENT:
			while(my($k,$v)=each %$args){
				$attrs{$k} = $v;
			}
		}
	}
	return \%attrs;
}	

sub dxmenu {
    my $c = shift;
	my $attrs = parse_attributs( $c, [qw(id @items onItemClick)], @_ );
	my $id = delete($attrs->{id}) // new_id( $c, $attrs );	
	dxbind( $c, 'dxMenu' => $id => $attrs);
}


sub dxloadpanel {
    my $c = shift;
	my $attrs = parse_attributs( $c, [qw(id message)], @_ );
	my $id = delete($attrs->{id}) // new_id( $c, $attrs );	
	dxbind( $c, 'dxLoadPanel' => $id => $attrs);
}


sub dxbutton {
    my $c = shift;
	my $attrs = parse_attributs( $c, [qw(id text onClick type)], @_ );
	my $id = delete($attrs->{id}) // new_id( $c, $attrs );	
	dxbind( $c, 'dxButton' => $id => $attrs);
}


sub dxdatagrid{
	my $c = shift;
	my $attrs = parse_attributs( $c, [qw(id dataSource)], @_ );
	my $id = delete($attrs->{id}) // new_id( $c, $attrs );
	my @extensions;
	#dxbind( $c, 'dxDataGrid' => $id => $attrs, [ $dataSource ]);
	if ($attrs->{dataSource} && ref($attrs->{dataSource}) eq '') {
		my $dataSource = delete $attrs->{dataSource};
		#push @extensions, '$.getJSON("' . $dataSource . '",function(data){$("#'.$id.'").dxDataGrid({ dataSource: data });});';
		#$attrs->{dataSource} = \'[]';	#protect string to be "stringified" within dxbind

		#\"" is to protect string to be "stringified" within dxbind
		$attrs->{dataSource} = \"{store:{type:'odata',url:'$dataSource'}}";
	}
	if (exists $attrs->{options}) {
		$attrs = $attrs->{options};
	}
	
	dxbind( $c, 'dxDataGrid' => $id => $attrs, \@extensions);
}


sub dxform{
	my $c = shift;
	my $attrs = parse_attributs( $c, [qw(id %formData @items)], @_ );
	my $id = delete($attrs->{id}) // new_id( $c, $attrs );
	
	dxbind( $c, 'dxForm' => $id => $attrs );
}


sub dxpopup{
	my $c = shift;
	my $attrs = parse_attributs( $c, [qw(id title contentTemplate)], @_ );
	my $id = delete($attrs->{id}) // new_id( $c, $attrs );
	
	dxbind( $c, 'dxPopup' => $id => $attrs );
}



sub mk_dxcontrol{
	my $dxControl = shift;
	my $generic = sub{
		my $c = shift;
		my $attrs = parse_attributs( $c, [qw(id value label)], @_ );
		my $id = delete($attrs->{id});
		if (my $name = $id) {
			$attrs->{name}=$name;
		}
		
		$id //= new_id( $c, $attrs );
	
		my (@before, @after);
		if(my $label = delete($attrs->{label})){
			push @before, '<div class="dx-field">';
			push @before, '<div class="dx-field-label">'.$label.'</div>';
			push @before, '<div class="dx-field-value">';
			push @after, '</div>';
			push @after, '</div>';
		}
		
		dxbind( $c, $dxControl => $id => $attrs, undef, \@before, \@after );	
	};
	
	{
		no strict 'refs';
		*{__PACKAGE__.'::'.lc $dxControl} = $generic;
	}
}


sub dxbuild {
	my $c    = shift;
	my %opts = @_;
	my $dxhelper = $c->stash('dxHelper') or return;
	if($dxhelper->{bindings}){
		out '<script language="javascript">'.
			($opts{js_prefix}//'$(window).on("load",function(){') .
			"\n".
			join("\n",
				 grep{ ($_//'') ne ''}
				 @$dxhelper{'before_bindings','bindings','after_bindings'}
			).
			"\n".
			($opts{js_sufix}//'});').'</script>';
	}
}


sub require_asset{
	my $c = shift;
	my $dxhelper = $c->stash('dxHelper') or return;
	
	push @{ $dxhelper->{required_assets} }, $_ for @_;
	
	return $c;
}


sub required_assets{
	my $c = shift;
	my $dxhelper = $c->stash('dxHelper') or return;
	my $required_assets = $dxhelper->{required_assets} // [];
	my $results = Mojo::ByteStream->new();
	ASSET:
	for my $asset (@$required_assets){
		#not sure about how to simulate " %= asset 'resource' " that we can use in template rendering, 
		#nor how to output multiple Mojo::ByteStream objets at a time (is returning required ?)
		$$results .= ${ $c->asset($asset) };
	}
	return $results;
}

sub prepend_js{
	my ($c, @js) = @_;
	my $dxhelper = $c->stash('dxHelper') or return;
	for(@js){
		$dxhelper->{before_bindings} .= "\n" if $INDENT_BINDING;
		$dxhelper->{before_bindings} .= $_;
	}
}


sub append_js{
	my ($c, @js) = @_;
	my $dxhelper = $c->stash('dxHelper') or return;
	for(@js){
		$dxhelper->{after_bindings} .= "\n" if $INDENT_BINDING;
		$dxhelper->{after_bindings} .= $_;
	}
}

sub register {
	my ( $self, $app, $args ) = @_;
	my $tp = $args->{tag_prefix};
	
	#build generic dx-controls
	mk_dxcontrol( "dx$_" ) for @generic_controls;
	
	SUB_NO_PREFIX:
	for my $subname ( @without_prefix ){
		my $lc_name = lc $subname;
		my $sub = __PACKAGE__->can( $lc_name );
		unless($sub){
			$app->log->debug(__PACKAGE__." helper '$lc_name' does not exists!");
			next SUB_NO_PREFIX;
		}
		$app->helper( $lc_name => $sub );
	}

	SUB_WITH_PREFIX:
	for my $subname ( @with_prefix ){
		my $lc_name = lc $subname;
		my $sub = __PACKAGE__->can( 'dx' . $lc_name );
		unless($sub){
			$app->log->debug(__PACKAGE__." helper 'dx$lc_name' does not exists!");
			next SUB_WITH_PREFIX;
		}
		say STDERR "## adding helper '$tp$lc_name'" if DEBUG;
		$app->helper( $tp . $lc_name => $sub );
		say STDERR "## adding helper '$tp$subname'" if DEBUG and $args->{tag_camelcase};
		$app->helper( $tp . $subname => $sub ) if $args->{tag_camelcase};
	}
	
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Mojolicious::Plugin::DevexpressHelpers::Helpers - Helpers for Devexpress controls are defined here

=head1 VERSION

version 0.163572

=head1 SUBROUTINES/METHODS

=head2 out

Output string in template

	out '<div id="'.$id.'"></div>';

=head2 new

Internal usage.

	my $dxHelper = Mojolicous::Plugin::DevexpressHelpers::Helpers->new;
	$c->stash( dxHelper => $dxHelper );

=head2 indent_binding

	$dxHelper->indent_binding( 1 );

=head2 add_binding

Internal usage.

	$dxHelper->add_binding($binding, [$binding2,...] );

=head2 next_id

Internal usage.

	my $next_id_number = $dxHelper->next_id;

=head2 new_id

Internal usage.

	my $new_id = $dxHelper->new_id;

=head2 dxbind

Internal usage.

	dxbind( $c, 'dxButton' => $id => $attrs, \@extensions);

Produce a div tag with an computed id, which will be binded to
a dxButton with C<$attrs> attributs at call to dxbuild.

=head2 parse_attributs

Internal usage

	my $attrs = parse_attributs( $c, \@implicit_arguments, @attributs )

=head2 dxmenu C< $id, \@items ,[ \%options ]>

	%= dxmenu mainMenu => [{ id => 1, text => 'Back', iconSrc => '/images/back.png', link => 'javascript:location.back();' }], \q% function(e){return e.itemData.link;} %

=head2 dxloadpanel C<[ $id, [ $message ] ], [ \%options ]>

	%= dxloadpanel myLoadPanel => 'Please wait'

=head2 dxbutton C<[ $id, [ $text, [ $onclick ] ] ], [ \%options ]>

	%= dxbutton myButtonId => 'My button' => q{ function (){ alert('onClick!'); } }
	
	%= dxbutton undef, 'My button' => '/some/url'
	
	%= dxbutton {
			id      => myId,
			text    => 'My button',
			onClick => q{ function (){ alert('onClick!'); } },
			type    => 'danger',
			icon    => 'user'
		};

=head2 dxdatagrid C<[ $id, [ $datasource, ] ] [ \%opts ]>

	%= dxdatagrid 'myID' => '/products.json', { columns => [qw( name description price )] }
	
	%= dxdatagrid undef, '/products.json'
	
	%= dxdatagrid { id => myId, dataSource => '/products.json' }

The following syntaxe allow to specify all options from a javascript object.
B<Note: It will ignore all other options specified in the hash reference.>

	%= dxdatagrid myId, { options => 'JSFrameWork.gridsOptions.myResource' }

=head2 dxform C<[ $id, [ $formData, [ \@itemsSpecs ], [ \%opts ] ] ]>

	%= dxform myForm => \%formData;
	
	%= dxform myForm => \%formData, { colCount => 2 };
	
	%= dxform myForm => \%formData, \%itemsSpecs;
	
	%= dxform myForm => \%formData, \%itemsSpecs, { colCount => '3' };

=head2 dxpopup C<[ $id, [ $title, [ $contentTemplate, ] ] ], [\%opts]>

	%= dxpopup myPopupID => 'Popup Title', \q{function(contentElement){
			contentElement.append('<p>Hello!</p>');
		}};

=head2 mk_dxcontrol C<$dxControlName>

In this package:

	mk_dxcontrol('dxNumberBox');
	mk_dxcontrol('dxSwitch');
	mk_dxcontrol('dxTextBox');
	mk_dxcontrol('dxLookup');
	mk_dxcontrol('dxSelectBox');

In your template:

	%= dxnumberbox 'age' => $value => 'Age: ', { placeHolder => 'Enter your age' }
	%= dxswitch 'mySwitch' => $boolean_value => 'Enabled: '
	%= dxtextbox 'name' => $value => 'Name: ', { placeHolder => 'Type a name' }
	%= dxlookup 'name' => $value => 'Name: ', { dataSource => $ds, valueExpr=> $ve, displayExpr => $de }
	%= dxselectbox 'name' => $value => 'Name: ', { dataSource => $ds, valueExpr=> $ve, displayExpr => $de }

=head2 dxbuild C<[ js_prefix => '$(function(){', js_sufix => '});' ]>

Build the binding between jQuery and divs generated by plugin helpers such as dxbutton.
It is should to be called in your template just before you close the body tag.
Optional named argument C<js_prefix> and C<js_sufix> can be specified to override
standard javascript wrapper.

	<body>
		...
		%= dxbuild
	</body>

=head2 require_asset @assets

Used to specify one or more assets dependencies, that will be appended on call to required_assets.
This function need 'AssetPack' plugin to be configurated in your application.

in your template:

	<body>
		...
		%= require_asset 'MyScript.js'
		...
	</body>

in your layout:

	<head>
		...
		%= required_assets
		...
	</head>

=head2 required_assets

Add assets that was specified by calls to require_asset.
See require_asset for usage.

=head2 prepend_js

Prepend javascript code to dx-generated code

	append_js 'alert("before dx-controls initialisation");'

=head2 append_js 

Append javascript code to dx-generated code

	append_js 'alert("after dx-controls initialisation");'

=head2 register

Register our helpers

=head1 AUTHOR

Nicolas Georges <xlat@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2015 by Nicolas Georges.

This is free software, licensed under:

  The MIT (X11) License

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.