Thursday, June 26, 2014

Spring MVC - Thymeleaf - Bootstrap - Twitter Flight - II.1

Source Code | See Application
You can see the full description of the aplication and the other two entries related here

Initial configuration

The web.xml and twitterFlightExample-servlet.xml will be the same as the shown previously.

Controller

We will use the following controller:
@Controller
public class Main1Controller {
    @Autowired
    ProductService productService;
    @Autowired
    MainInfo mainInfo;

    @RequestMapping("/main1")
    public ModelAndView main() {
        return new ModelAndView("main1");
    }

    @RequestMapping(value = "/changePage1", method = RequestMethod.POST)
    @ResponseBody
    public String changePage(@RequestParam("page") Integer page) {
        logger.info("Changing page to:{}", page);
        mainInfo.setCurrentPage(page);
        return "Ok";
    }

    @RequestMapping(value = "/changeFilter1", method = RequestMethod.POST)
    @ResponseBody
    public String changeFilter(@RequestParam("filter") String filter, @RequestParam("value") String value) {
        logger.info("Changing filter to:({}:{})", filter, value);
        mainInfo.updateFilter(filter, value);
        SummaryInfo summaryInfo = productService.createSummaryInfoFor(mainInfo.getFilter());
        mainInfo.setSummaryInfo(summaryInfo);
        return "" + mainInfo.getPageInfo().getTotalPages();
    }

    @RequestMapping("/getProductList1")
    public ModelAndView getProductList() {
        List<Product> products = productService.findProducts(mainInfo.getFilter(), mainInfo.getPageInfo());
        Map model = new HashMap<>();
        model.put("products", products);
        model.put("pageInfo", mainInfo.getPageInfo());
        return new ModelAndView("productList", model);
    }

    @RequestMapping("/getBrandList1")
    public ModelAndView getBrandList() {
        Map model = new HashMap<>();
        model.put("brandList", mainInfo.getSummaryInfo().getBrandSummaryList());
        return new ModelAndView("brandList", model);
    }

    @RequestMapping("/getBreadcrumb1")
    public ModelAndView getBreadcrumb() {
        Map model = new HashMap<>();
        model.put("filterList", mainInfo.getFilter().getFilterEntryList());
        return new ModelAndView("breadcrumb", model);
    }

    @RequestMapping("/getProductInfo1")
    public ModelAndView getProductInfo(@RequestParam("product") Long productGkey) {
        logger.debug("Getting product info for:{}", productGkey);
        List<ProductPriceHistory> productPriceHistoryList = productService.getProductPricesHistoryFor(productGkey);
        Map model = new HashMap();
        model.put("productPriceHistoryList", productPriceHistoryList);
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm");
        model.put("displayDateFormatter", dateFormat);
        return new ModelAndView("productDetail", model);
    }
}

Notes:

  • main() It renders the main page; in this case this page only contains html code so none model needs to be sent.
  • changePage(@RequestParam("page") Integer page) It changes the page to the value sent as parameter and returns Ok
  • changeFilter(@RequestParam("filter") String filter, @RequestParam("value") String value) It adds the filter sent as parameter, regenerates the info base summary on the new filter; and returns the number of total pages.
  • ModelAndView getProductList() It returns the html fragment that contains the product list.
  • ModelAndView getBrandList() It returns the html fragment that contains the brand list.
  • ModelAndView getBreadcrumb() It returns the html fragment that contains the Breadcrumb.
  • getProductInfo(@RequestParam("product") Long productGkey) It returns the html fragment that contains the price information of the product.

View

<html xmlns="http://www.w3.org/1999/xhtml"
      class="csstransforms csstransforms3d csstransitions">
<head>
    ...
    <link rel="stylesheet" href="/bower_components/twitterflightexample/twitterflighexample.css"/>
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="/bower_components/bootstrap/css/bootstrap.min.css"/>
    <link rel="stylesheet" href="/bower_components/bootstrap/css/bootstrap-responsive.min.css"/>
    <link rel="stylesheet" href="/bower_components/nprogress/nprogress.css"/>
</head>
<body>
    ...
<script data-main="app1/js/requireMain.js" src="/bower_components/requirejs/require.js"></script>
</body>

Notes:

  • It is a simple html page. Thymeleaf is not needed.
  • bootstrap-paginator: is used to implement the pagination
  • nprogress: is used to show the progress bar at the top during the execution of ajax
  • data-main="app1/js/requireMain.js"; is the main js file. It will be used Require.js (AMD) in order to manage the dependencies and components.

Twitter Flight Components

Define all components:

app.js
define([
    './data/data_provider',
    './ui/product_list',
    './ui/brand_list',
    './ui/breadcrumb',
    './ui/search_box',
    './ui/paginator'
], function (DataProviderData, ProductListUI, BrandListUI, BreadcrumbUI, SearchBoxUI, PaginatorUI) {
    var initialize = function () {
        DataProviderData.attachTo(document);
        ProductListUI.attachTo('#product-zone');
        BrandListUI.attachTo('#brand-zone');
        BreadcrumbUI.attachTo('#breadcrumb-zone');
        SearchBoxUI.attachTo('#search-zone');
        PaginatorUI.attachTo('#paginator-zone');
    };
    return {
        initialize: initialize
    };
});

Notes:

  • The data components normally are attached to document
  • The UI components are attached to the respective html zones.

Data Components

DataProviderData

define(
    ...
    function (defineComponent) {
        return defineComponent(dataProvider);
        function dataProvider() {
            ...
            this.onUiPageDataChanged = function (ev, data) {
                console.info("Changing page to:" + data.page);
                var dataString = 'page=' + data.page;
                $.ajax({
                    type: "POST",
                    url: "/changePage1.htm",
                    data: dataString,
                    success: function (data) {
                        console.info("ChangPage response:" + data);
                        $(document).trigger("pageDataChanged")
                    }
                });
            };

            this.onUiFilterChanged = function (ev, data) {
                console.info("Changing filter:" + data.filter + " value:" + data.value);
                var dataString = 'filter=' + data.filter + "&value=" + data.value;
                $.ajax({
                    type: "POST",
                    url: "/changeFilter1.htm",
                    data: dataString,
                    success: function (data) {
                        console.info("ChangeFilter response. New number of pages:" + data);
                        $(document).trigger("dataChanged", { resultSize: data })
                    }
                });
            }

            this.after('initialize', function () {
                this.on('uiPageDataChanged', this.onUiPageDataChanged);
                this.on('uiFilterChanged', this.onUiFilterChanged);
            });
        }
    }
)
Notes:
  • this.after('initialize', function () Registers listeners for the events: uiPageDataChanged,uiFilterChanged
  • this.onUiPageDataChanged = function (ev, data) When the user changes the page, this method will execute an ajax call to: changePage1.htm in order to change the page on the server.
  • this.onUiFilterChanged = function (ev, data) When the user changes any filter, this method will execute an ajax call to: changeFilter1.htm in order to change the filter on the server.

UI Components

SearchBoxUI

define(
    ...
    function (defineComponent) {
        return defineComponent(searchBox);
        function searchBox() {
            this.defaultAttrs({
                txtSelector: '#txtSearch',
                btnSelector: '#btnSearch'
            });

            this.searchOnClick = function (e) {
                e.preventDefault();
                this.executeSearch();
            };

            this.executeSearch = function () {
                var $txtSearch = this.select('txtSelector');
                var textToSearch = $txtSearch.val().trim();
                if (!textToSearch) {
                    return;
                }
                $txtSearch.val('');
                this.trigger("uiFilterChanged", {filter: "name", value: textToSearch})
            }

            this.after('initialize', function () {
                this.on('click', {
                    btnSelector: this.searchOnClick
                });
                this.trigger("uiFilterChanged", {filter: "name", value: ""})
            });
        }
    }
);
Notes:
  • this.after('initialize', function ()
    • Registers listeners for the native click event, on the button with id:btnSearch.
    • Trigger the event uiFilterChanged. This is needed in order to populate the initial page with all the products.
  • this.executeSearch = function () Gets the value that is set in the textfield and then triggers the event: uiFilterChanged

BrandListUI

define(
...    
function (defineComponent) {
    return defineComponent(brandList);
    function brandList() {
        this.onClick = function (ev, data) {
            ev.preventDefault();
            var $anchor = $(ev.target).closest('.list-group-item').find('a');
            var brandId = $anchor.attr("data-brand-id");
            this.trigger("uiFilterChanged", {filter: "brand", value: brandId})
        };

        this.renderItems = function (ev, data) {
            console.info("On Rendering Brands");
            var $brandList = this.$node;
            $.ajax({
                type: "GET",
                url: "/getBrandList1.htm",
                success: function (data) {
                    $brandList.html(data);
                }
            });
        }

        this.after('initialize', function () {
            this.on('click', this.onClick);
            this.on(document, 'dataChanged', this.renderItems);
        });
    }
}
Notes:
  • this.after('initialize', function () Registers listeners for the native click event, and the custom event dataChanged on document
  • this.onClick = function (ev, data) Gets the closest anchor to the html element where the click was done. The brandId is gotten and the event uiFilterChanged is triggered with the brandId as parameter
  • this.renderItems = function (ev, data) Gets the new component's html from the server, for it executes the call /getBrandList1.htm and then replaces the component's html with the one returned by the server

BreadcrumbUI

define(
    ...
    function (defineComponent) {
    return defineComponent(breadcrumb);
    function breadcrumb() {
        ...
        this.onClick = function (ev, data) {
            ev.preventDefault();
            var filterIdx = $(ev.target).attr("data-filter-idx");
            this.trigger("uiFilterChanged", {filter: "breadcrumbIdx", value: filterIdx})
        }
        this.renderItems = function (ev, data) {
            console.info("On Rendering Breadcrumb");
            var $breadcrumb = this.$node;
            $.ajax({
                type: "GET",
                url: "/getBreadcrumb1.htm",
                success: function (data) {
                    $breadcrumb.html(data);
                }
            });
        }
        this.after('initialize', function () {
            this.on('click', this.onClick);
            this.on(document, 'dataChanged', this.renderItems);
        });
    }
}
Notes:
  • this.after('initialize', function () Registers listeners for the native click event, and the custom event dataChanged on document
  • this.onClick = function (ev, data) Gets the data-filter-idx and triggers the event uiFilterChanged with the breadcrumbIdx as parameter
  • this.renderItems = function (ev, data) Gets the new component's html from the server, for it execute the call /getBreadcrumb1.htm and then replaces the component's html with the one returned by the server

ProductListUI

define(
function (defineComponent) {
    return defineComponent(productList);
    function productList() {
        this.onClick = function (ev) {
            var $productItem = $(ev.target).closest('.list-group-item');
            var productGkey = $productItem.attr("data-product-gkey");
            showProductInfo($productItem, productGkey);
        }

        function showProductInfo(obj, productGkey) {
            var $this = $(obj);
            var $collapse = $this.find('.collapse');
            var dataString = 'product=' + productGkey;
            var spanId = '#spn' + productGkey;
            var currentText = $(spanId).text();
            if (currentText == "Expand") {
                $(spanId).text("Collapse");
                $.ajax({
                    type: "GET",
                    url: "/getProductInfo1.htm",
                    data: dataString,
                    success: function (data) {
                        $collapse.html(data);
                        $collapse.show();
                    }
                });
            } else {
                $(spanId).text("Expand")
                $collapse.hide();
            }
            return false;
        }

        this.renderItems = function (ev, data) {
            console.info("On Rendering Products");
            var $productList = this.$node;
            $.ajax({
                type: "GET",
                url: "/getProductList1.htm",
                success: function (data) {
                    $productList.html(data);
                }
            });
        }

        this.after('initialize', function () {
            this.on(document, 'pageDataChanged', this.renderItems);
            this.on(document, 'dataChanged', this.renderItems);
            this.on('click', this.onClick);
        });
    }
}
Notes:
  • this.after('initialize', function () Registers listeners for the native click event, and the custom event dataChanged and pageDataChanged on document
  • this.onClick = function (ev, data) Gets the closest html element with class: list-group-item from it gets the productGkey and then call the method showProductInfo
  • showProductInfo(obj, productGkey) Checks if the price history zone is expanded. If so, then it collapses and if not, then it calls the server with /getProductInfo1.htm and then expands and fills the zone of price history with the server response
  • this.renderItems = function (ev, data) Gets the new component's html from the server. To do this, it executes the call /getProductList1.htm and then replaces the component's html with the one returned by the server

PaginatorUI

define(
function (defineComponent) {
    return defineComponent(paginator);
    function paginator() {
        this.defaultAttrs({
            options: {
                bootstrapMajorVersion: 3,
                currentPage: 1,
                totalPages: 2,
                numberOfPages: 10
            }
        });

        this.onPageChanged = function (ev, lastPage, currentPage) {
            this.trigger('uiPageDataChanged', {page: (currentPage - 1)});
        };

        this.onDataChanged = function (ev, data) {
            var pagesNumber = data.resultSize;
            var $paginator = $('#paginator');
            if (pagesNumber > 1) {
                $paginator.show();
                this.attr.options.currentPage = 1;
                this.attr.options.totalPages = pagesNumber;
                $paginator.bootstrapPaginator(this.attr.options);
            } else {
                $paginator.hide();
            }
        };

        this.after('initialize', function () {
            this.on('page-changed', this.onPageChanged);
            this.on(document, 'dataChanged', this.onDataChanged);
        });
    }
}
);
Notes:
This component is a wrapper of bootstrap-paginator
  • this.after('initialize', function () Registers listeners for the bootstrap-paginator's page-changed event, and the custom event dataChanged on document
  • this.onPageChanged = function (ev, lastPage, currentPage) Triggers the uiPageDataChanged event with the page (-1) sent by bootstrap-paginator
  • this.onDataChanged = function (ev, data) Gets the new pages number and based on this the paginator is hidden or shown with the new pages number.

16 comments:

Praylin S said...

Great blog! This is really helpful for my reference. Do share more such posts and keep us updated. Looking forward to more informative blogs from you.
C C++ Training in Chennai
C Training in Chennai
IoT Training in Chennai
IoT Courses in Chennai
Oracle Training in Chennai
Oracle Training institute in chennai
C C++ Training in Tambaram
C C++ Training in Velachery

cynthiawilliams said...

Learned a lot from your post and it is really good. Share more tech updates regularly.
AWS Training in Chennai
DevOps Training in Chennai
Angularjs Training in Chennai
RPA Training in Chennai
Blue Prism Training in Chennai
UiPath Training in Chennai
Data Science Course in Chennai
ccna Training in Chennai
R Programming Training in Chennai

Ethical Hacking Course said...


After reading your article I was amazed. I know that you explain it very well. And I hope that other readers will also experience how I feel after reading your article.
Ethical Hacking Course in Bangalore

Best Data Science Courses In Bangalore said...

I am really enjoying reading your well written articles. It looks like you spend a lot of effort and time on your blog. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work.
Best Data Science Courses In Bangalore

technology said...

The only thing you get to do initially is what you ought to do, i.e. programming. Later comes, the technical aspects. data science course in india

Huongkv said...

Aivuvu chuyên vé máy bay, tham khảo

vé máy bay đi Mỹ giá bao nhiêu

đã có chuyến bay từ mỹ về việt nam chưa

vé máy bay đi đà nẵng bao nhiêu tiền

vé máy bay hải phòng nha trang vietjet

gia ve di phu quoc

lionelmessi said...

Hi, Thanks for sharing wonderful information...

AWS Training in Hyderabad

Digital Marketing Course said...

I am a new user of this site, so here I saw several articles and posts published on this site, I am more interested in some of them, will provide more information on these topics in future articles.
Digital Marketing Course in Bangalore

Professional Training Institute said...

I am a new user of this site, so here I saw several articles and posts published on this site, I am more interested in some of them, will provide more information on these topics in future articles

Digital Marketing Course in Bangalore

Dettifoss IT Solutions said...

Thanks for sharing wonderful stuff.
servicenow training and placement in hyderabad

traininginstitute said...

Very good points you wrote here..Great stuff...I think you've made some truly interesting points.Keep up the good work.
data science course

Educational Courses said...

Very good message. I came across your blog and wanted to tell you that I really enjoyed reading your articles.

Cloud Computing Certification in Bangalore

Educational Training and Learning said...

It's like you've got the point right, but forgot to include your readers. Maybe you should think about it from different angles.

Best Cyber Security Training Institute in Bangalore

Dettifoss IT Solutions said...

I would you like to say thank you so much for my heart. Really amazing and impressive post you have the share. Please keep sharing.
ServiceNow Training in Chennai

Career Programs Excellence said...

Register for the Data Scientist courses in Bangalore and learn to build your Data Science and Machine learning workflows. Build a portfolio of work to have on your resume with live projects which are supported by an industry-relevant curriculum. Get Access to our learning management system (LMS) that provides you with all the material and assignments that will help you master all the concepts for you to solve any problem related to deciphering the hidden meaning in data.

Data Science in Bangalore

Educational Courses said...

Enroll in the Data Science course near me to learn the handling of huge amounts of data by analyzing it with the help of analytical tools. This field offers ample job profiles to work as a Data Architect, Data Administrator, Data Analyst, Business Analyst, Data Manager, and BI Manager. Step into an exciting career in the field of Data Science and achieve great heights by acquiring the right knowledge and skills to formulate solutions to business problems.


Data Science Course in Delhi

Post a Comment