{ Hutility }

Hutility

Categories
Uncategorized

HuLib

HuLib

HuLib is a library we have developed for our internal use in consulting applications. The purpose of HuLib was to avoid duplication in our consulting programs by abstracting it in an easy to use library.

The majority of our applications are written in C# and deal with AccPac, file reading/writing and SQL interaction (both with csqry and otherwise) so this is where the main focus of the library’s features lie.

Sage (AccPac) features

A complication with our writing of Sage consulting applications has been AccPac’s API. While very versatile, the API has an awkward syntax which requires a non-standard form of error detection and in the case of CSQRY (which is used to send sql queries through the Sage API) requires setting properties without explanation for it to work.

To deal with this we have abstracted away the Sage connection (you can now create a new connection with

Connections

{"type":"elementor","siteurl":"https://mktg.hutility.com/wp-json/","elements":[{"id":"59419b0","elType":"widget","isInner":false,"isLocked":false,"settings":{"editor":"<pre>class TestData\n{\n\t[DisplayName(\"Name2\")]\n\tpublic string Name { get; set; }\n\tpublic string Description { get; set; }\n\tpublic decimal Amount { get; set; }\n}\n\nExportTableBuilder&lt;TestData&gt; builder = new ExportTableBuilder&lt;TestData&gt;()\n\t.AddColumn(d =&gt; d.Name) // Header will be Name2\n\t.AddColumn(d =&gt; d.Description) // Header will be Description\n\t.AddColumn(\"Amount2\", d =&gt; d.Amount); // Header will be Amount2</pre>","typography_typography":"custom","typography_font_family":"Open Sans","typography_font_size":{"unit":"px","size":18},"typography_line_height":{"unit":"em","size":1.7},"_animation_delay":600,"_margin":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":false},"align":"left","_animation":"fadeInUp","align_mobile":"center","text_color":"#251313","typography_font_size_mobile":{"unit":"px","size":16,"sizes":[]},"_background_background":"classic","_background_color":"#C6C1C1","drop_cap":"","text_columns":"","text_columns_tablet":"","text_columns_mobile":"","column_gap":{"unit":"px","size":"","sizes":[]},"column_gap_tablet":{"unit":"px","size":"","sizes":[]},"column_gap_mobile":{"unit":"px","size":"","sizes":[]},"align_tablet":"","typography_font_size_tablet":{"unit":"px","size":"","sizes":[]},"typography_font_weight":"","typography_text_transform":"","typography_font_style":"","typography_text_decoration":"","typography_line_height_tablet":{"unit":"em","size":"","sizes":[]},"typography_line_height_mobile":{"unit":"em","size":"","sizes":[]},"typography_letter_spacing":{"unit":"px","size":"","sizes":[]},"typography_letter_spacing_tablet":{"unit":"px","size":"","sizes":[]},"typography_letter_spacing_mobile":{"unit":"px","size":"","sizes":[]},"typography_word_spacing":{"unit":"px","size":"","sizes":[]},"typography_word_spacing_tablet":{"unit":"em","size":"","sizes":[]},"typography_word_spacing_mobile":{"unit":"em","size":"","sizes":[]},"text_shadow_text_shadow_type":"","text_shadow_text_shadow":{"horizontal":0,"vertical":0,"blur":10,"color":"rgba(0,0,0,0.3)"},"drop_cap_view":"default","drop_cap_primary_color":"","drop_cap_secondary_color":"","drop_cap_shadow_text_shadow_type":"","drop_cap_shadow_text_shadow":{"horizontal":0,"vertical":0,"blur":10,"color":"rgba(0,0,0,0.3)"},"drop_cap_size":{"unit":"px","size":5,"sizes":[]},"drop_cap_space":{"unit":"px","size":10,"sizes":[]},"drop_cap_border_radius":{"unit":"%","size":"","sizes":[]},"drop_cap_border_width":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"drop_cap_typography_typography":"","drop_cap_typography_font_family":"","drop_cap_typography_font_size":{"unit":"px","size":"","sizes":[]},"drop_cap_typography_font_size_tablet":{"unit":"px","size":"","sizes":[]},"drop_cap_typography_font_size_mobile":{"unit":"px","size":"","sizes":[]},"drop_cap_typography_font_weight":"","drop_cap_typography_text_transform":"","drop_cap_typography_font_style":"","drop_cap_typography_text_decoration":"","drop_cap_typography_line_height":{"unit":"px","size":"","sizes":[]},"drop_cap_typography_line_height_tablet":{"unit":"em","size":"","sizes":[]},"drop_cap_typography_line_height_mobile":{"unit":"em","size":"","sizes":[]},"drop_cap_typography_word_spacing":{"unit":"px","size":"","sizes":[]},"drop_cap_typography_word_spacing_tablet":{"unit":"em","size":"","sizes":[]},"drop_cap_typography_word_spacing_mobile":{"unit":"em","size":"","sizes":[]},"_title":"","_margin_tablet":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_margin_mobile":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_padding":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_padding_tablet":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_padding_mobile":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_element_width":"","_element_width_tablet":"","_element_width_mobile":"","_element_custom_width":{"unit":"%","size":"","sizes":[]},"_element_custom_width_tablet":{"unit":"px","size":"","sizes":[]},"_element_custom_width_mobile":{"unit":"px","size":"","sizes":[]},"_element_vertical_align":"","_element_vertical_align_tablet":"","_element_vertical_align_mobile":"","_position":"","_offset_orientation_h":"start","_offset_x":{"unit":"px","size":"0","sizes":[]},"_offset_x_tablet":{"unit":"px","size":"","sizes":[]},"_offset_x_mobile":{"unit":"px","size":"","sizes":[]},"_offset_x_end":{"unit":"px","size":"0","sizes":[]},"_offset_x_end_tablet":{"unit":"px","size":"","sizes":[]},"_offset_x_end_mobile":{"unit":"px","size":"","sizes":[]},"_offset_orientation_v":"start","_offset_y":{"unit":"px","size":"0","sizes":[]},"_offset_y_tablet":{"unit":"px","size":"","sizes":[]},"_offset_y_mobile":{"unit":"px","size":"","sizes":[]},"_offset_y_end":{"unit":"px","size":"0","sizes":[]},"_offset_y_end_tablet":{"unit":"px","size":"","sizes":[]},"_offset_y_end_mobile":{"unit":"px","size":"","sizes":[]},"_z_index":"","_z_index_tablet":"","_z_index_mobile":"","_element_id":"","_css_classes":"","_animation_tablet":"","_animation_mobile":"","animation_duration":"","_transform_rotate_popover":"","_transform_rotateZ_effect":{"unit":"px","size":"","sizes":[]},"_transform_rotateZ_effect_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_rotateZ_effect_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_rotate_3d":"","_transform_rotateX_effect":{"unit":"px","size":"","sizes":[]},"_transform_rotateX_effect_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_rotateX_effect_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_rotateY_effect":{"unit":"px","size":"","sizes":[]},"_transform_rotateY_effect_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_rotateY_effect_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_perspective_effect":{"unit":"px","size":"","sizes":[]},"_transform_perspective_effect_tablet":{"unit":"px","size":"","sizes":[]},"_transform_perspective_effect_mobile":{"unit":"px","size":"","sizes":[]},"_transform_translate_popover":"","_transform_translateX_effect":{"unit":"px","size":"","sizes":[]},"_transform_translateX_effect_tablet":{"unit":"px","size":"","sizes":[]},"_transform_translateX_effect_mobile":{"unit":"px","size":"","sizes":[]},"_transform_translateY_effect":{"unit":"px","size":"","sizes":[]},"_transform_translateY_effect_tablet":{"unit":"px","size":"","sizes":[]},"_transform_translateY_effect_mobile":{"unit":"px","size":"","sizes":[]},"_transform_scale_popover":"","_transform_keep_proportions":"yes","_transform_scale_effect":{"unit":"px","size":"","sizes":[]},"_transform_scale_effect_tablet":{"unit":"px","size":"","sizes":[]},"_transform_scale_effect_mobile":{"unit":"px","size":"","sizes":[]},"_transform_scaleX_effect":{"unit":"px","size":"","sizes":[]},"_transform_scaleX_effect_tablet":{"unit":"px","size":"","sizes":[]},"_transform_scaleX_effect_mobile":{"unit":"px","size":"","sizes":[]},"_transform_scaleY_effect":{"unit":"px","size":"","sizes":[]},"_transform_scaleY_effect_tablet":{"unit":"px","size":"","sizes":[]},"_transform_scaleY_effect_mobile":{"unit":"px","size":"","sizes":[]},"_transform_skew_popover":"","_transform_skewX_effect":{"unit":"px","size":"","sizes":[]},"_transform_skewX_effect_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_skewX_effect_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_skewY_effect":{"unit":"px","size":"","sizes":[]},"_transform_skewY_effect_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_skewY_effect_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_flipX_effect":"","_transform_flipY_effect":"","_transform_rotate_popover_hover":"","_transform_rotateZ_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_rotateZ_effect_hover_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_rotateZ_effect_hover_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_rotate_3d_hover":"","_transform_rotateX_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_rotateX_effect_hover_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_rotateX_effect_hover_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_rotateY_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_rotateY_effect_hover_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_rotateY_effect_hover_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_perspective_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_perspective_effect_hover_tablet":{"unit":"px","size":"","sizes":[]},"_transform_perspective_effect_hover_mobile":{"unit":"px","size":"","sizes":[]},"_transform_translate_popover_hover":"","_transform_translateX_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_translateX_effect_hover_tablet":{"unit":"px","size":"","sizes":[]},"_transform_translateX_effect_hover_mobile":{"unit":"px","size":"","sizes":[]},"_transform_translateY_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_translateY_effect_hover_tablet":{"unit":"px","size":"","sizes":[]},"_transform_translateY_effect_hover_mobile":{"unit":"px","size":"","sizes":[]},"_transform_scale_popover_hover":"","_transform_keep_proportions_hover":"yes","_transform_scale_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_scale_effect_hover_tablet":{"unit":"px","size":"","sizes":[]},"_transform_scale_effect_hover_mobile":{"unit":"px","size":"","sizes":[]},"_transform_scaleX_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_scaleX_effect_hover_tablet":{"unit":"px","size":"","sizes":[]},"_transform_scaleX_effect_hover_mobile":{"unit":"px","size":"","sizes":[]},"_transform_scaleY_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_scaleY_effect_hover_tablet":{"unit":"px","size":"","sizes":[]},"_transform_scaleY_effect_hover_mobile":{"unit":"px","size":"","sizes":[]},"_transform_skew_popover_hover":"","_transform_skewX_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_skewX_effect_hover_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_skewX_effect_hover_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_skewY_effect_hover":{"unit":"px","size":"","sizes":[]},"_transform_skewY_effect_hover_tablet":{"unit":"deg","size":"","sizes":[]},"_transform_skewY_effect_hover_mobile":{"unit":"deg","size":"","sizes":[]},"_transform_flipX_effect_hover":"","_transform_flipY_effect_hover":"","_transform_transition_hover":{"unit":"px","size":"","sizes":[]},"motion_fx_transform_x_anchor_point":"","motion_fx_transform_x_anchor_point_tablet":"","motion_fx_transform_x_anchor_point_mobile":"","motion_fx_transform_y_anchor_point":"","motion_fx_transform_y_anchor_point_tablet":"","motion_fx_transform_y_anchor_point_mobile":"","_background_color_stop":{"unit":"%","size":0,"sizes":[]},"_background_color_b":"#f2295b","_background_color_b_stop":{"unit":"%","size":100,"sizes":[]},"_background_gradient_type":"linear","_background_gradient_angle":{"unit":"deg","size":180,"sizes":[]},"_background_gradient_position":"center center","_background_image":{"url":"","id":"","size":""},"_background_image_tablet":{"url":"","id":"","size":""},"_background_image_mobile":{"url":"","id":"","size":""},"_background_position":"","_background_position_tablet":"","_background_position_mobile":"","_background_xpos":{"unit":"px","size":0,"sizes":[]},"_background_xpos_tablet":{"unit":"px","size":0,"sizes":[]},"_background_xpos_mobile":{"unit":"px","size":0,"sizes":[]},"_background_ypos":{"unit":"px","size":0,"sizes":[]},"_background_ypos_tablet":{"unit":"px","size":0,"sizes":[]},"_background_ypos_mobile":{"unit":"px","size":0,"sizes":[]},"_background_attachment":"","_background_repeat":"","_background_repeat_tablet":"","_background_repeat_mobile":"","_background_size":"","_background_size_tablet":"","_background_size_mobile":"","_background_bg_width":{"unit":"%","size":100,"sizes":[]},"_background_bg_width_tablet":{"unit":"px","size":"","sizes":[]},"_background_bg_width_mobile":{"unit":"px","size":"","sizes":[]},"_background_video_link":"","_background_video_start":"","_background_video_end":"","_background_play_once":"","_background_play_on_mobile":"","_background_privacy_mode":"","_background_video_fallback":{"url":"","id":"","size":""},"_background_slideshow_gallery":[],"_background_slideshow_loop":"yes","_background_slideshow_slide_duration":5000,"_background_slideshow_slide_transition":"fade","_background_slideshow_transition_duration":500,"_background_slideshow_background_size":"","_background_slideshow_background_size_tablet":"","_background_slideshow_background_size_mobile":"","_background_slideshow_background_position":"","_background_slideshow_background_position_tablet":"","_background_slideshow_background_position_mobile":"","_background_slideshow_lazyload":"","_background_slideshow_ken_burns":"","_background_slideshow_ken_burns_zoom_direction":"in","_background_hover_background":"","_background_hover_color":"","_background_hover_color_stop":{"unit":"%","size":0,"sizes":[]},"_background_hover_color_b":"#f2295b","_background_hover_color_b_stop":{"unit":"%","size":100,"sizes":[]},"_background_hover_gradient_type":"linear","_background_hover_gradient_angle":{"unit":"deg","size":180,"sizes":[]},"_background_hover_gradient_position":"center center","_background_hover_image":{"url":"","id":"","size":""},"_background_hover_image_tablet":{"url":"","id":"","size":""},"_background_hover_image_mobile":{"url":"","id":"","size":""},"_background_hover_position":"","_background_hover_position_tablet":"","_background_hover_position_mobile":"","_background_hover_xpos":{"unit":"px","size":0,"sizes":[]},"_background_hover_xpos_tablet":{"unit":"px","size":0,"sizes":[]},"_background_hover_xpos_mobile":{"unit":"px","size":0,"sizes":[]},"_background_hover_ypos":{"unit":"px","size":0,"sizes":[]},"_background_hover_ypos_tablet":{"unit":"px","size":0,"sizes":[]},"_background_hover_ypos_mobile":{"unit":"px","size":0,"sizes":[]},"_background_hover_attachment":"","_background_hover_repeat":"","_background_hover_repeat_tablet":"","_background_hover_repeat_mobile":"","_background_hover_size":"","_background_hover_size_tablet":"","_background_hover_size_mobile":"","_background_hover_bg_width":{"unit":"%","size":100,"sizes":[]},"_background_hover_bg_width_tablet":{"unit":"px","size":"","sizes":[]},"_background_hover_bg_width_mobile":{"unit":"px","size":"","sizes":[]},"_background_hover_video_link":"","_background_hover_video_start":"","_background_hover_video_end":"","_background_hover_play_once":"","_background_hover_play_on_mobile":"","_background_hover_privacy_mode":"","_background_hover_video_fallback":{"url":"","id":"","size":""},"_background_hover_slideshow_gallery":[],"_background_hover_slideshow_loop":"yes","_background_hover_slideshow_slide_duration":5000,"_background_hover_slideshow_slide_transition":"fade","_background_hover_slideshow_transition_duration":500,"_background_hover_slideshow_background_size":"","_background_hover_slideshow_background_size_tablet":"","_background_hover_slideshow_background_size_mobile":"","_background_hover_slideshow_background_position":"","_background_hover_slideshow_background_position_tablet":"","_background_hover_slideshow_background_position_mobile":"","_background_hover_slideshow_lazyload":"","_background_hover_slideshow_ken_burns":"","_background_hover_slideshow_ken_burns_zoom_direction":"in","_background_hover_transition":{"unit":"px","size":"","sizes":[]},"_border_border":"","_border_width":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_width_tablet":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_width_mobile":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_color":"","_border_radius":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_radius_tablet":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_radius_mobile":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_box_shadow_box_shadow_type":"","_box_shadow_box_shadow":{"horizontal":0,"vertical":0,"blur":10,"spread":0,"color":"rgba(0,0,0,0.5)"},"_box_shadow_box_shadow_position":" ","_border_hover_border":"","_border_hover_width":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_hover_width_tablet":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_hover_width_mobile":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_hover_color":"","_border_radius_hover":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_radius_hover_tablet":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_border_radius_hover_mobile":{"unit":"px","top":"","right":"","bottom":"","left":"","isLinked":true},"_box_shadow_hover_box_shadow_type":"","_box_shadow_hover_box_shadow":{"horizontal":0,"vertical":0,"blur":10,"spread":0,"color":"rgba(0,0,0,0.5)"},"_box_shadow_hover_box_shadow_position":" ","_border_hover_transition":{"unit":"px","size":"","sizes":[]},"_mask_switch":"","_mask_shape":"circle","_mask_image":{"url":"","id":"","size":""},"_mask_notice":"","_mask_size":"contain","_mask_size_tablet":"","_mask_size_mobile":"","_mask_size_scale":{"unit":"%","size":100,"sizes":[]},"_mask_size_scale_tablet":{"unit":"px","size":"","sizes":[]},"_mask_size_scale_mobile":{"unit":"px","size":"","sizes":[]},"_mask_position":"center center","_mask_position_tablet":"","_mask_position_mobile":"","_mask_position_x":{"unit":"%","size":0,"sizes":[]},"_mask_position_x_tablet":{"unit":"px","size":"","sizes":[]},"_mask_position_x_mobile":{"unit":"px","size":"","sizes":[]},"_mask_position_y":{"unit":"%","size":0,"sizes":[]},"_mask_position_y_tablet":{"unit":"px","size":"","sizes":[]},"_mask_position_y_mobile":{"unit":"px","size":"","sizes":[]},"_mask_repeat":"no-repeat","_mask_repeat_tablet":"","_mask_repeat_mobile":"","hide_desktop":"","hide_tablet":"","hide_mobile":""},"defaultEditSettings":{"defaultEditRoute":"content"},"elements":[],"widgetType":"text-editor","editSettings":{"defaultEditRoute":"content","panel":{"activeTab":"content","activeSection":"section_editor"}},"htmlCache":"\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-text-editor elementor-clearfix elementor-inline-editing\" data-elementor-setting-key=\"editor\" data-elementor-inline-editing-toolbar=\"advanced\">\n\t\t\t\t<pre>class TestData\n{\n\t[DisplayName(\"Name2\")]\n\tpublic string Name { get; set; }\n\tpublic string Description { get; set; }\n\tpublic decimal Amount { get; set; }\n}\n\nExportTableBuilder&lt;TestData&gt; builder = new ExportTableBuilder&lt;TestData&gt;()\n\t.AddColumn(d =&gt; d.Name) // Header will be Name2\n\t.AddColumn(d =&gt; d.Description) // Header will be Description\n\t.AddColumn(\"Amount2\", d =&gt; d.Amount); // Header will be Amount2</pre>\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t"}]}

The first line above creates a new connection via the sign on manager, the second one lets you set the user / password / company manually.

HuAPConnection is Disposable (you can have it in a using statement and it will dispose of it once it is out of scope) and is the focal point for sage interactions from this point on.

Views

using (HuView arcus = connection.OpenView("AR0024"))
{
    arcus["IDCUST"] = "1200";
    if (arcus.Read())
    {
        arcus["TEXTSNAM"] = arcus["TEXTSTRE1"] // set name to address1;
        view.Update();
    }
}

View interactions are similar to using the accpac view objects directly but it is wrapped by an HuView and is retrieved from an HuAPConnection. Like connections, HuViews are disposable, however the connection also keeps track of all opened views so if you had not disposed of the view, when the connection closes it will close it for you!

In addition to OpenView, there is another similar function that returns a view called GetView, this allows you to repeatedly use the same view (if there was one that has been disposed it will clear that view and return it to you. This allows you to have a GetView function within a for loop and you do not have to worry about the overhead on creating new views, the connection will notice there is now an available view of the type you requested, clear it and return it to you.

While the raw Sage view is still accessible from the HuView, most interactions will use HuView directly. As seen above, fields are assigned and retrieved via an indexor rather than the traditional way with the Sage api: <view>.Fields.FieldByID[<id>].set_Value(<value>) which can be a bit overkill and makes code less readable when you have many of these operations.

Additionally, when performing operations with the HuView, any exceptions will be raised as an AccPacException. This way you do not need to deal with catching COMExceptions and reading from the Sage api directly the error / warning feeds which was what we had to do previously and adds much more boilerplate code than we would want.

UI

We use mainly WPF for our desktop applications now. Using the accpac finder has presented us with many issues and as such we have developed our own version.

<hulib:HuWFEC Connection="{Binding Connection}" ViewID="BK0001" Value="{Binding Bank}"/>

The above is a line taken from one of our applications using it. You can bind to connection, value, filter. You can also set field ids that are displayed and many other settings you would expect to be present for an AccPac finder.

It looks very similar to the Sage finder (the top of the image shows the textbox and button to open the finder, the pop up below is what is shown when the button is clicked.

Other Features

There are many other quality of life features (eg a function that creates a dictionary based on optional field key-value pairs). In addition in the sections below there are features that help integrate back into AccPac (eg Mapping)

File IO

Often our customizations revolve around exporting or importing data to/from files. To assist in our development we have abstracted some of this functionality.

Mapping

One of the commonalities between the different forms of files and even SQL and Sage views is that there is a feature of HuLib we called simply Mapping that will work with them. Mapping is a way to use annotations on your code to very easily extract data from different sources. For example, see: Reading CSV files

This way changes in the data source can be dealt with in the model that will store the data directly instead of having to both change the model and the code that would normally be importing it.

CSV Files

Using the CSVFile class you can easily load a file with just the file name then you have it all split for you (includes custom delimiters, escape character parsing). On top of that there is a mappable implementation meaning you can use mapping to just label a model class you want the data to go into and have it populated in just a couple lines.

Excel Files

We have a number of different excel utilities. The latest one we have been using is the BufferedExcelReader (also, writer). Using this you do not have to manually interact with the excel interop API and can instead just use ReadString, ReadLine as if it was a standard file. If you want to access specific parts of the sheet, you can do it based on column/row with the column being in standard excel letter form or in numerical form. Or you can use the mapping implementation to immediately load data from excel into a collection of your model.

One of the main advantages of the BufferedExcelReader over our previous utilities is the buffering portion. Interop actions between excel are expensive, so the BufferedExcelReader deals with this by preloading the whole file in bulk and allowing you to interact with it in the way you want.

Tools

We have developed a number of tools to help generate code that works with HuLib immediately. These include a website whereby uploading a CSV file it provides you with a model class for that file complete with the mapping annotations. A web based view info for Sage information that allows searching. A web based application that can convert the generated macro code from AccPac into C# and then into HuLib code (Most sage code in the various blog posts here are a result of running macro code through this converter and pulling out the needed parts).

And More!

This is just a summary of some of the main features of HuLib, since we are using this as a general library for our commoon requirements from day to day we are adding more to it all the time. It would be difficult to enumerate every feature present in it.

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Categories
Uncategorized

Writing IEnumerable data to CSV/Excel as a table (HuLib 1.0.7 feature)

Writing IEnumerable data to CSV/Excel as a table (HuLib 1.0.7 feature)

A common requirement for us is exporting data to excel or CSV files. While it is not too daunting of a task, the frequency of it prompted me to look at a more concise way of writing it.

Based on the established practices we first get all the data we need to export into some form of IEnumerable and then have a function to export it to the desired target.

My previous approach was to use one of our writer classes (ie BufferedExcelWriter or CSVWriter to write each field one by one. This is quick to write and looks like this:

  1. Write each header one by one with .Write(“header name”)
  2. Loop through rows of the IEnumerable
    1. For each of the columns, we need to write the value
    2. Write a newline

So, this is not too difficult to implement but you end up with many lines and the code for the headers is separate from that of the data in the table. This can lead to a bit more complication in the event we need to rearrange, add/remove columns since it becomes quite easy to accidentally misalign columns.

I thought it would be nice if instead, we could just have a single call that includes defining the columns and what data they should have rather than directly iterating through them. This may not be the best way to go in all cases – you are omitting looping in favour of just using a single function to get each value, but for our exports, it usually satisfies our requirements.

Example

Say we have the following model class:

class TestData
{
	public string Name { get; set; }
	public string Description { get; set; }
	public decimal Amount { get; set; }

	public TestData(string name, string description, decimal amount)
	{
		Name = name;
		Description = description;
		Amount = amount;
	}
}

We want to export all 3 properties per line.

The old approach would look like this (for Excel):

BufferedExcelWriter writer = new BufferedExcelWriter();
writer.AddSheet("Sheet1");
writer.Write("Name");
writer.Write("Description");
writer.WriteLine("Amount");
writer.WriteLine();
foreach (TestData data in testData)
{
	writer.Write(data.Name);
	writer.Write(data.Description);
	writer.Write(data.Amount);
}

writer.Save("New File.xlsx");

This works and gives us what we want but can get tedious.

The new approach makes use of some new things in HuLib, namely ExportTableBuilder:

BufferedExcelWriter writer = new BufferedExcelWriter();
writer.AddSheet("Sheet1");

writer.WriteTable(testData, new ExportTableBuilder()
	.AddColumn("Name", d => d.Name)
	.AddColumn("Description", d => d.Description)
	.AddColumn("Amount", d => d.Amount));
	
writer.Save("New File.xlsx");

Now we have all of the writing content to the file down to a single call! You can see that it is very clear which header corresponds to which value, and it is impossible to misalign the header to the value.

Seeing that this would reduce the number of calls for generating content to 1 in most cases I took it one step further and added extensions to CSV and Excel to export to a file directly:

testData.ToExcel("New File.xlsx", new ExportTableBuilder()
	.AddColumn("Name", d => d.Name)
	.AddColumn("Description", d => d.Description)
	.AddColumn("Amount", d => d.Amount));

Now you can get a list of objects into a CSV / Excel file without worrying about which class to use.

Note: there is currently no support for formatting (in Excel)

Functions of note:

// Some list of whatever model you want
List testData = new List()
{
	new TestData("Apple", "A fruit", 30),
	new TestData("Banana", "Also a fruit", 22),
	new TestData("Flamingo", "Not a fruit", 4),
};

// Table definition
ExportTableBuilder builder = new ExportTableBuilder()
	.AddColumn("Name", d => d.Name)
	.AddColumn("Description", d => d.Description)
	.AddColumn("Amount", d => d.Amount);

// Export to Excel
Excel.Export(testData, "New File.xlsx", builder);
testData.ToExcel("New File.xlsx", builder); // This and the line above are equivalent

// Export to CSV
CSV.Export(testData, "New File.csv", builder);
testData.ToCSV("New File.csv", builder); // This and the line above are equivalent

These functions are made available via an extension to the IWriter interface which both BufferedExcelWriter and CSVWriter now implement. If you want to enable the table export to another type of writer just implement IWriter and you will have access to the WriteTable functionality.

Extra – Naming based on property

Just added in the latest preview 1.0.7 build is the ability to infer the header name based on the property or field. If no header is specified and there is only an expression, the header will be set to the provided property or field’s display name attribute (if it exists) or its own name.

class TestData
{
	[DisplayName("Name2")]
	public string Name { get; set; }
	public string Description { get; set; }
	public decimal Amount { get; set; }
}

ExportTableBuilder<TestData> builder = new ExportTableBuilder<TestData>()
	.AddColumn(d => d.Name) // Header will be Name2
	.AddColumn(d => d.Description) // Header will be Description
	.AddColumn("Amount2", d => d.Amount); // Header will be Amount2

This is just a convenience addition that does not add more functionality but can be more convenient in some cases.

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Sage300c Web Screen Customizations

Sage300c Web Screen Customizations

We’ll be starting a new series of posts on Sage300c web screen customizations.

The purpose is to have a resource that has code snippets, gotchas and tips to help our internal developers and possibly anyone else looking to get started. Sage has provided their Sage 300 Web SDK as open source on Github, but we’re hoping to add some more code examples from a different perspective.

It’s a big move for 3rd party developers to go from VB6 screen customizations to the new web screens, so hopefully we can help some developers save some time on the way!

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Getting Started with Sage300c Customization Wizards

Getting Started with Sage300c Customization Wizards

To kick things off, we’ll quickly go over the two main wizards you’ll use to create a web screen customization. Sample projects that use these are in the SDK on Github, but we’ll illustrate them here for a quick reference. You’ll find an example of an OE Order Entry screen customization, in the SDK, at: /docs/customization/Sage300SDK_WebScreenOrderEntryCustomizationTutorial.docx

The first wizard you’ll run, is for the client side web UI. You can find this wizard in the SDK, at: /bin/wizards/Sage.CA.SBS.ERP.Sage300.CustomizationWizard.exe

Installing

Navigate to your Sage 300c Admin dashboard. If you’re running locally that would be at http://localhost/Sage300/Admin 

From that screen, import your zip file.

Once imported, click on your customization, and assign it to be available to a company.

Log out of the admin dashboard, and navigate back to the main Sage300c application at:

http://localhost/Sage300

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Packaging and Installing a Sage 300c Web Screen Customization

Packaging and Installing a Sage 300c Web Screen Customization​

So you have your customization files and a custom controller project. How do you get this installed?

It’s actually fairly easy, and Sage has a nice way to install them into individual companies for the web screens.

Packaging

Find the dll compiled by your controller project. In our case, it’s named Hutility.CU.Web.dll.

Also, find the bootstrapper XML file in your project. In our case, it’s named HutilityCUBootstrapper.xml.

Package those 2 files, and along with the UI’s manifest, js and XML files into a zip.

Installing

Navigate to your Sage 300c Admin dashboard. If you’re running locally that would be at http://localhost/Sage300/Admin 

From that screen, import your zip file.

Once imported, click on your customization, and assign it to be available to a company.

Log out of the admin dashboard, and navigate back to the main Sage300c application at:

http://localhost/Sage300

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Debugging Sage 300c C# Controller Code

Debugging Sage 300c C# Controller Code

In Visual Studio, attach to the process w3wp.exe

You may need to run Visual Studio as administrator.

Once attached to the process, navigate to your customized screen on the Sage 300c web UI.

In Visual Studio, open ensure the symbols are loaded for the controller dll.

In the above code, btnFinderCustomerTest would be the id of the button that triggers the finder, and txtFinderCustomerTest would be id of the textbox where the finder result value is placed.

The button and textbox DOM elements can be defined using the Sage UI Customization wizard or defined via javascript as we’ve described here.

Use your custom screen, and any breakpoints you’ve set should be hit.

Troubleshooting:

Make sure the pdb and the dll are matching versions, or VS will refuse to load symbols.

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Sage 300c Customization Tip: Adding UI Elements Using Javascript

Sage 300c Customization Tip: Adding UI Elements Using Javascript

Here’s a way to add UI elements to your customization using javascript, bypassing the UI wizard.

One reason you might not want to use the wizard, is that it may be missing certain HTML elements. One example is adding a password input textbox. As of August 2019, the wizard doesn’t provide this option.

In the below example, we’re adding a textbox and button combo to initialize later on as a finder. We’re also adding a textbox for a user id, and a corresponding password. We’ll be adding these right before the row where the Post button is, on the OE Order Entry screen.

HutilityOrderEntryCustomizationUI = {
    initCustomFinders: function () {
        sg.viewFinderHelper.setViewFinder("btnFinderCustomerTest", "txtFinderCustomerTest",
            {
                viewID: "AR0024",
                viewOrder: 0,
                displayFieldNames: ["IDCUST"],
                returnFieldNames: ["IDCUST"],
                filter: null,
                initKeyValues: [],
                parentValAsInitKey: true
            });
    },
}
// Initial Entry
$(function () {
    HutilityOrderEntryCustomizationUI.init();
    HutilityOrderEntryCustomizationUI.initCustomFinders();
});

In the above code, btnFinderCustomerTest would be the id of the button that triggers the finder, and txtFinderCustomerTest would be id of the textbox where the finder result value is placed.

The button and textbox DOM elements can be defined using the Sage UI Customization wizard or defined via javascript as we’ve described here.

The code above can be done more elegantly, please excuse it for the sake of illustration purposes!

This is nothing specific to Sage 300c web development, just showing it as an option that can be done.

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Initializing Finders in Sage 300c Web Customizations​

Initializing Finders in Sage 300c Web Customizations​

This example shows a trimmed down portion of a javascript customization file, that only includes finder initialization.

In this example, the finder is for AR Customers, where the finder will only show the Customer ID field, and return that same field as the value.

HutilityOrderEntryCustomizationUI = {
    initCustomFinders: function () {
        sg.viewFinderHelper.setViewFinder("btnFinderCustomerTest", "txtFinderCustomerTest",
            {
                viewID: "AR0024",
                viewOrder: 0,
                displayFieldNames: ["IDCUST"],
                returnFieldNames: ["IDCUST"],
                filter: null,
                initKeyValues: [],
                parentValAsInitKey: true
            });
    },
}
// Initial Entry
$(function () {
    HutilityOrderEntryCustomizationUI.init();
    HutilityOrderEntryCustomizationUI.initCustomFinders();
});

In the above code, btnFinderCustomerTest would be the id of the button that triggers the finder, and txtFinderCustomerTest would be id of the textbox where the finder result value is placed.

The button and textbox DOM elements can be defined using the Sage UI Customization wizard or defined via javascript as we’ve described here.

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name

Initializing Finders in Sage 300c Web Customizations

This example shows a trimmed down portion of a javascript customization file, that only includes finder initialization.

In this example, the finder is for AR Customers, where the finder will only show the Customer ID field, and return that same field as the value.

HutilityOrderEntryCustomizationUI = {
    initCustomFinders: function () {
        sg.viewFinderHelper.setViewFinder("btnFinderCustomerTest", "txtFinderCustomerTest",
            {
                viewID: "AR0024",
                viewOrder: 0,
                displayFieldNames: ["IDCUST"],
                returnFieldNames: ["IDCUST"],
                filter: null,
                initKeyValues: [],
                parentValAsInitKey: true
            });
    },
}
// Initial Entry
$(function () {
    HutilityOrderEntryCustomizationUI.init();
    HutilityOrderEntryCustomizationUI.initCustomFinders();
});

In the above code, btnFinderCustomerTest would be the id of the button that triggers the finder, and txtFinderCustomerTest would be id of the textbox where the finder result value is placed.

The button and textbox DOM elements can be defined using the Sage UI Customization wizard or defined via javascript as we’ve described here.

Categories
Uncategorized

Sage 300c Customization: Modals, Dialog Boxes and Prompts

Sage 300c Customization: Modals, Dialog Boxes and Prompts

Plain old javascript dialogs and prompts cannot be readily used in customizations, as the web screens run inside of frames.

For example, running the below code:

 var r = confirm("Are you sure you want to post for customer [" + data.model.CustomerNumber + "] from group [" + data.model.CustomerGroupCode + "]?");

Will be ignored by the browser, and the following will be logged in the console:

Ignored call to 'confirm()'. The document is sandboxed, and the 'allow-modals' keyword is not set.

To avoid this, we must use one of the numerous dialog box options available to us in Sage’s global.js.

There are A LOT of functions, so we’ll add them here as we learn how to use them.

Here are a few to start:

sg.utls.showValidationMessage

Using this, simply shows an error validation message.

It can used like this:

sg.utls.showKendoConfirmationDialog

Using this shows a yes/no prompt.

It can be used like this:

  var approverUserId = $('#txtApproverUserId').val();
    var approverPw = $('#txtApproverPw').val();
    var customerNumber = orderEntryUI.finderData.CustomerNumber;
    if(approverUserId && approverPw)
    {
        sg.utls.showKendoConfirmationDialog(function () {
                sg.utls.ajaxPost(url, { user: approverUserId, pw: approverPw, customerNumber: customerNumber }, HutilityOrderEntryCustomizationUICallback.jbtest);
            },
            null, "Approve order with user [" + approverUserId + "].", "Demo");
    }
    else {
        sg.utls.showValidationMessage("Pleaser the credit approver credentials");

The above example shows a confirmation dialog if two textboxes have been filled in, otherwise it shows a validation error message. The confirmation dialog takes in 3 arguments: the function to run upon a “Yes”, the function to run upon a “No”, and the prompt message.

In this example, we’re sending an AJAX POST request to a custom controller if “Yes” is clicked.

The confirmation dialog shows like this:

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name
Categories
Uncategorized

Making Ajax POST Request in Sage 300c Customizations

Making Ajax POST Request in Sage 300c Customizations

Here’s how to make a POST request, using Sage’s supplied ajaxPost function:

sg.utls.ajaxPost(url, { user: approverUserId, pw: approverPw, customerNumber: customerNumber}, HutilityOrderEntryCustomizationUICallback.jbtest);

Here we’re supplying it with a url, our post data, and a callback function to handle the response.

Originally we tried to make POST requests using JQuery, but ran into issues. After inspecting the requests in Chrome Dev Tools, we found that the Sage ajaxPost includes tokens in the request that must be acting as CSRF tokens.

We can help!
Contact Us Today

Please enable JavaScript in your browser to complete this form.
Name