├── .gitignore ├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── deployment.xml ├── dictionaries │ └── jonon.xml ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── Run_POS_Sample__Spring_boot_.xml ├── uiDesigner.xml └── vcs.xml ├── README.md ├── angular-pos-sample.iml ├── database.sql ├── docs ├── orderEntry.png ├── orderList.png └── paymentDialog.png ├── items.csv ├── pom.xml └── src └── main ├── java └── com │ └── reonsoftware │ └── possample │ ├── Application.java │ ├── db │ ├── ItemDao.java │ ├── OrderDao.java │ ├── OrderFilter.java │ └── TenderDao.java │ ├── models │ ├── DetailedLineItem.java │ ├── DetailedOrder.java │ ├── Item.java │ ├── Order.java │ └── Tender.java │ └── rest │ ├── ItemController.java │ ├── OrderController.java │ ├── SettingsController.java │ └── TenderController.java ├── resources └── logback.xml └── webapp ├── css ├── fileUpload.css ├── orderList.css ├── ordersSidebar.css ├── styles.css └── tenderPaymentDialog.css ├── fileUpload.html ├── index.html ├── js ├── controllers │ ├── file-upload.controller.js │ ├── order-entry.controller.js │ ├── order-list.controller.js │ └── tender-payment.controller.js ├── node_modules │ ├── angular-animate │ │ ├── README.md │ │ ├── angular-animate.js │ │ ├── angular-animate.min.js │ │ ├── angular-animate.min.js.map │ │ ├── bower.json │ │ ├── index.js │ │ └── package.json │ ├── angular-ui-bootstrap │ │ ├── .npmignore │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ ├── ui-bootstrap-csp.css │ │ ├── ui-bootstrap-tpls.js │ │ ├── ui-bootstrap-tpls.min.js │ │ ├── ui-bootstrap.js │ │ └── ui-bootstrap.min.js │ ├── angular │ │ ├── README.md │ │ ├── angular-csp.css │ │ ├── angular.js │ │ ├── angular.min.js │ │ ├── angular.min.js.gzip │ │ ├── angular.min.js.map │ │ ├── bower.json │ │ ├── index.js │ │ └── package.json │ └── ng-file-upload │ │ ├── .jshintrc │ │ ├── .npmignore │ │ ├── Gruntfile.js │ │ ├── LICENSE │ │ ├── README.md │ │ ├── demo │ │ ├── .idea │ │ │ ├── .name │ │ │ ├── artifacts │ │ │ │ ├── ng_file_upload_demo_server_war.xml │ │ │ │ └── ng_file_upload_demo_server_war_exploded.xml │ │ │ ├── codeStyleSettings.xml │ │ │ ├── compiler.xml │ │ │ ├── copyright │ │ │ │ └── profiles_settings.xml │ │ │ ├── encodings.xml │ │ │ ├── libraries │ │ │ │ ├── Maven__com_google_appengine_appengine_api_1_0_sdk_1_9_18.xml │ │ │ │ ├── Maven__com_google_appengine_appengine_endpoints_1_9_18.xml │ │ │ │ ├── Maven__commons_fileupload_commons_fileupload_1_2.xml │ │ │ │ ├── Maven__javax_inject_javax_inject_1.xml │ │ │ │ └── Maven__javax_servlet_servlet_api_2_5.xml │ │ │ ├── misc.xml │ │ │ ├── modules.xml │ │ │ ├── vcs.xml │ │ │ └── workspace.xml │ │ ├── C# │ │ │ ├── UploadController.js │ │ │ ├── UploadHandler.ashx │ │ │ └── UploadHandler.ashx.cs │ │ ├── pom.xml │ │ └── src │ │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── df │ │ │ │ └── angularfileupload │ │ │ │ ├── CORSFilter.java │ │ │ │ ├── FileUpload.java │ │ │ │ └── S3Signature.java │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ ├── jdoconfig.xml │ │ │ │ └── persistence.xml │ │ │ └── log4j.properties │ │ │ └── webapp │ │ │ ├── WEB-INF │ │ │ ├── appengine-web.xml │ │ │ ├── logging.properties │ │ │ └── web.xml │ │ │ ├── common.css │ │ │ ├── crossdomain.xml │ │ │ ├── donate.html │ │ │ ├── favicon.ico │ │ │ ├── img │ │ │ ├── tea.jpg │ │ │ └── tea.png │ │ │ ├── index.html │ │ │ └── js │ │ │ ├── FileAPI.flash.swf │ │ │ ├── FileAPI.js │ │ │ ├── FileAPI.min.js │ │ │ ├── ng-file-upload-all.js │ │ │ ├── ng-file-upload-all.min.js │ │ │ ├── ng-file-upload-shim.js │ │ │ ├── ng-file-upload-shim.min.js │ │ │ ├── ng-file-upload.js │ │ │ ├── ng-file-upload.min.js │ │ │ ├── ng-img-crop.css │ │ │ ├── ng-img-crop.js │ │ │ └── upload.js │ │ ├── index.js │ │ ├── nuget │ │ ├── NuGet.exe │ │ ├── Package.nuspec │ │ ├── build.bat │ │ └── nuget.sh │ │ ├── package.json │ │ ├── release.sh │ │ ├── src │ │ ├── FileAPI.flash.swf │ │ ├── FileAPI.js │ │ ├── data-url.js │ │ ├── drop.js │ │ ├── exif.js │ │ ├── model.js │ │ ├── resize.js │ │ ├── select.js │ │ ├── shim-elem.js │ │ ├── shim-filereader.js │ │ ├── shim-upload.js │ │ ├── upload.js │ │ └── validate.js │ │ └── test │ │ ├── .bowerrc │ │ ├── bower.json │ │ ├── index.html │ │ └── spec │ │ └── test.js ├── package.json ├── posSample.js └── services │ ├── order-persistence.factory.js │ ├── order-status.factory.js │ └── view-manager.factory.js ├── orderList.html ├── orderSidebar.html └── tenderPaymentDialog.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | /*.iws 3 | /*.ids 4 | /.idea/workspace.xml 5 | /.idea/compiler.xml 6 | /.idea/libraries/ 7 | /.idea/artifacts/ 8 | /.idea/dataSources* 9 | /.idea/sqldialects* 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Windows 15 | Thumbs.db 16 | 17 | # Maven 18 | target/ 19 | *.pom.xml.versionsBackup 20 | 21 | # Misc 22 | builds/ 23 | out/ 24 | dist/ 25 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | pos-sample -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/dictionaries/jonon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | angularjs 5 | bootstrapcdn 6 | dropwizard 7 | flexbox 8 | intelli 9 | netdna 10 | onejar 11 | onstott 12 | possample 13 | reonsoftware 14 | screenshot 15 | screenshots 16 | subquery 17 | tpls 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run_POS_Sample__Spring_boot_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ,-.----. ,----.. 3 | \ / \ / / \ .--.--. .--.--. ____ ,--, 4 | | : \ / . : / / '. / / '. ,' , `.,-.----. ,--.'| 5 | | | .\ : . / ;. \ : /`. / | : /`. / ,-+-,.' _ |\ / \ | | : 6 | . : |: |. ; / ` ; | |--` ; | |--` ,-+-. ; , ||| : |: : ' 7 | | | \ :; | ; \ ; | : ;_ | : ;_ ,--.--. ,--.'|' | ||| | .\ :| ' | ,---. 8 | | : . /| : | ; | '\ \ `. \ \ `. / \ | | ,', | |,. : |: |' | | / \ 9 | ; | |`-' . | ' ' ' : `----. \ `----. \.--. .-. | | | / | |--' | | \ :| | : / / | 10 | | | ; ' ; \; / | __ \ \ | __ \ \ | \__\/: . . | : | | , | : . |' : |__ . ' / | 11 | : ' | \ \ ', / / /`--' / / /`--' / ," .--.; | | : | |/ : |`-'| | '.'|' ; /| 12 | : : : ; : / '--'. / '--'. / / / ,. | | | |`-' : : : ; : ;' | / | 13 | | | : \ \ .' `--'---' `--'---' ; : .' \| ;/ | | : | , / | : | 14 | `---'.| `---` | , .-./'---' `---'.| ---`-' \ \ / 15 | `---` `--`---' `---` `----' 16 | 17 | 18 | Overview 19 | -------- 20 | 21 | This is a sample restaurant ordering/point-of-sale system, written in Angular with a Spring REST backend. 22 | Bootstrap adds some UI styling and Flexbox (CSS3) is used for most of the layout. 23 | 24 | Installation 25 | ------------ 26 | 27 | 1. Install MySQL 5.6.5 or later (due to http://stackoverflow.com/a/10603198/132374). 28 | 2. Execute the database.sql DDL to set up the tables 29 | 3. You can change the DB connection details in `com.reonsoftware.possample.Application`. 30 | By default, the database is expected to be called 'pos' located on localhost. 31 | The database user 'pos' with password 'pos' is used for access. 32 | 4. Spring Boot is used as the server, and the main class is `com.reonsoftware.possample.Application`. 33 | Start the server and browse to index.html. Use the CSV import feature to import the contents of items.csv 34 | (in this folder) into the items table. 35 | 36 | Also: 37 | 38 | - Flexbox was used for UI layout, so please view the site using a modern browser 39 | - The project was created using IntelliJ and Maven 40 | 41 | Notes 42 | ----- 43 | 44 | In a real web app, things would be a bit different: 45 | 46 | - I enjoy TDD & BDD, but haven't set up Java/Javascript unit tests for this sample yet 47 | - The JS isn't checking for failed AJAX calls or other errors 48 | - It'd be nice to make use of Angular directives (maybe make menu items into a reusable directive/tag) 49 | - NPM was used to pull in dependencies like Angular. Typically I'd exclude the node_modules folder from source 50 | control, but in this case I'm including it so that you don't have to install Node on your system. 51 | 52 | Screenshots 53 | ----------- 54 | 55 | *List of orders:* 56 | ![List of orders](/docs/orderList.png?raw=true "List of orders") 57 | 58 | *Order entry:* 59 | ![Order entry](/docs/orderEntry.png?raw=true "Order entry") 60 | 61 | *Payment dialog:* 62 | ![Payment dialog](/docs/paymentDialog.png?raw=true "Payment dialog") 63 | -------------------------------------------------------------------------------- /angular-pos-sample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /database.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `items` ( 2 | `item_id` INT(11) NOT NULL AUTO_INCREMENT, 3 | `name` VARCHAR(45) DEFAULT NULL, 4 | `price` DECIMAL(15, 2) DEFAULT NULL, 5 | PRIMARY KEY (`item_id`), 6 | UNIQUE KEY `item_name_uk` (`name`) 7 | ) 8 | ENGINE = InnoDB 9 | DEFAULT CHARSET = utf8; 10 | 11 | CREATE TABLE `orders` ( 12 | `order_id` INT(11) NOT NULL AUTO_INCREMENT, 13 | `order_number` INT(11) DEFAULT NULL, 14 | `number_assign_date` DATETIME DEFAULT NULL, 15 | PRIMARY KEY (`order_id`) 16 | ) 17 | ENGINE = InnoDB 18 | DEFAULT CHARSET = utf8; 19 | 20 | CREATE TABLE `order_line_items` ( 21 | `order_line_item_id` INT(11) NOT NULL AUTO_INCREMENT, 22 | `order_id` INT(11) NOT NULL, 23 | `item_id` INT(11) NOT NULL, 24 | `quantity` INT(11) NOT NULL, 25 | PRIMARY KEY (`order_line_item_id`), 26 | KEY `oli_items_fk_idx` (`item_id`), 27 | KEY `oli_orders_fk_idx` (`order_id`), 28 | CONSTRAINT `oli_items_fk` FOREIGN KEY (`item_id`) REFERENCES `items` (`item_id`) 29 | ON DELETE NO ACTION 30 | ON UPDATE NO ACTION, 31 | CONSTRAINT `oli_orders_fk` FOREIGN KEY (`order_id`) REFERENCES `orders` (`order_id`) 32 | ON DELETE NO ACTION 33 | ON UPDATE NO ACTION 34 | ) 35 | ENGINE = InnoDB 36 | DEFAULT CHARSET = utf8; 37 | 38 | CREATE TABLE `tender` ( 39 | `tender_id` INT(11) NOT NULL AUTO_INCREMENT, 40 | `order_id` INT(11) NOT NULL, 41 | `amount_tendered` DECIMAL(15, 2) NOT NULL, 42 | `change_due` DECIMAL(15, 2) NOT NULL, 43 | PRIMARY KEY (`tender_id`), 44 | KEY `tender_order_fk_idx` (`order_id`), 45 | CONSTRAINT `tender_order_fk` FOREIGN KEY (`order_id`) REFERENCES `orders` (`order_id`) 46 | ON DELETE NO ACTION 47 | ON UPDATE NO ACTION 48 | ) 49 | ENGINE = InnoDB 50 | DEFAULT CHARSET = utf8; 51 | -------------------------------------------------------------------------------- /docs/orderEntry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/docs/orderEntry.png -------------------------------------------------------------------------------- /docs/orderList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/docs/orderList.png -------------------------------------------------------------------------------- /docs/paymentDialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/docs/paymentDialog.png -------------------------------------------------------------------------------- /items.csv: -------------------------------------------------------------------------------- 1 | Item Name, Price 2 | Supreme Pizza, 16.99 3 | BBQ Chicken Pizza, 14.99 4 | Veggie Pizza, 12.99 5 | Meat Lover's Pizza, 17.99 6 | Hawaiian Pizza, 14.99 7 | Supreme Calzone, 8.99 8 | BBQ Chicken Calzone, 7.99 9 | Veggie Calzone, 6.99 10 | Meat Lover's Calzone, 8.99 11 | Hawaiian Calzone, 7.99 12 | Side Salad, 3.99 13 | Ceasar Salad, 4.99 14 | Cobb Salad, 4.99 15 | Chef Salad, 5.99 16 | Grilled Cheese, 3.99 17 | Coke, 1.00 18 | Diet Coke, 1.00 19 | Sprite, 1.00 20 | Dr. Pepper, 1.00 21 | Root Beer, 1.00 22 | 23 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.reonsoftware 8 | angular-pos-sample 9 | 1.0-SNAPSHOT 10 | war 11 | 12 | 13 | 14 | 15 | 16 | maven-compiler-plugin 17 | 3.3 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-maven-plugin 22 | 1.2.7.RELEASE 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-compiler-plugin 30 | 31 | 1.8 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | io.spring.platform 46 | platform-bom 47 | 1.1.4.RELEASE 48 | pom 49 | import 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-web 59 | 60 | 61 | org.springframework 62 | spring-jdbc 63 | 64 | 65 | 66 | org.apache.commons 67 | commons-csv 68 | 1.2 69 | 70 | 71 | 72 | mysql 73 | mysql-connector-java 74 | 5.1.37 75 | 76 | 77 | 78 | 79 | 80 | spring-releases 81 | https://repo.spring.io/libs-release 82 | 83 | 84 | 85 | 86 | spring-releases 87 | https://repo.spring.io/libs-release 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/Application.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample; 2 | 3 | import com.mysql.jdbc.Driver; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.jdbc.core.JdbcTemplate; 8 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 9 | import org.springframework.jdbc.datasource.SimpleDriverDataSource; 10 | 11 | /** 12 | * @author Jon Onstott 13 | * @since 10/31/2015 14 | */ 15 | @SpringBootApplication 16 | public class Application { 17 | 18 | @Bean 19 | JdbcTemplate getJdbcTemplate() { 20 | return new JdbcTemplate(createDataSource()); 21 | } 22 | 23 | @Bean 24 | NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() { 25 | return new NamedParameterJdbcTemplate(createDataSource()); 26 | } 27 | 28 | private SimpleDriverDataSource createDataSource() { 29 | SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); 30 | dataSource.setDriverClass(Driver.class); 31 | dataSource.setUrl("jdbc:mysql://localhost:3306/pos"); 32 | dataSource.setUsername("pos"); 33 | dataSource.setPassword("pos"); 34 | return dataSource; 35 | } 36 | 37 | public static void main(String[] args) { 38 | SpringApplication.run(Application.class, args); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/db/ItemDao.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.db; 2 | 3 | import com.reonsoftware.possample.models.DetailedLineItem; 4 | import com.reonsoftware.possample.models.Item; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | import org.springframework.jdbc.core.RowMapper; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.sql.ResultSet; 11 | import java.sql.SQLException; 12 | import java.util.List; 13 | 14 | /** 15 | * @author Jon Onstott 16 | * @since 11/1/2015 17 | */ 18 | @Component 19 | public class ItemDao { 20 | 21 | @Autowired 22 | private JdbcTemplate jdbcTemplate; 23 | 24 | public List getDetailedLineItems(long orderId) { 25 | String lineItemSql = "SELECT item_id, quantity FROM order_line_items WHERE order_id = ?"; 26 | return jdbcTemplate.query(lineItemSql, (itemRs, rowNum1) -> { 27 | long itemId = itemRs.getLong("item_id"); 28 | Item item = getItem(itemId); 29 | int quantity = itemRs.getInt("quantity"); 30 | return new DetailedLineItem(item, quantity); 31 | }, orderId); 32 | } 33 | 34 | private Item getItem(long itemId) { 35 | return jdbcTemplate.queryForObject("SELECT item_id, name, price FROM items WHERE item_id = ?", new ItemMapper(), itemId); 36 | } 37 | 38 | public List getItems() { 39 | return jdbcTemplate.query("SELECT item_id, name, price FROM items", new ItemMapper()); 40 | } 41 | 42 | public void createItem(Item item) { 43 | jdbcTemplate.update("INSERT INTO items(name, price) VALUES (?,?)", item.getName(), item.getPrice()); 44 | } 45 | 46 | private static class ItemMapper implements RowMapper { 47 | @Override 48 | public Item mapRow(ResultSet resultSet, int rowNum) throws SQLException { 49 | return new Item( 50 | resultSet.getLong("item_id"), 51 | resultSet.getString("name"), 52 | resultSet.getBigDecimal("price")); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/db/OrderDao.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.db; 2 | 3 | import com.reonsoftware.possample.models.DetailedLineItem; 4 | import com.reonsoftware.possample.models.DetailedOrder; 5 | import com.reonsoftware.possample.models.Order; 6 | import com.reonsoftware.possample.models.Tender; 7 | import com.reonsoftware.possample.rest.SettingsController; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.dao.EmptyResultDataAccessException; 12 | import org.springframework.jdbc.core.JdbcTemplate; 13 | import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; 14 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 15 | import org.springframework.jdbc.support.GeneratedKeyHolder; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.math.BigDecimal; 19 | import java.text.SimpleDateFormat; 20 | import java.util.Date; 21 | import java.util.List; 22 | 23 | /** 24 | * @author Jon Onstott 25 | * @since 11/1/2015 26 | */ 27 | @Component 28 | public class OrderDao { 29 | 30 | private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy h:mm a"); 31 | private static final Logger LOGGER = LoggerFactory.getLogger(OrderDao.class); 32 | 33 | @Autowired 34 | private JdbcTemplate jdbcTemplate; 35 | 36 | @Autowired 37 | private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 38 | 39 | @Autowired 40 | private ItemDao itemDao; 41 | 42 | public List getOrders(OrderFilter filter) { 43 | String filterSql = filter.getFilterSql(); 44 | String orderSql = "SELECT orders.*, tender.amount_tendered, tender.change_due " + 45 | "FROM orders " + 46 | "LEFT OUTER JOIN tender ON orders.order_id = tender.order_id " + 47 | (!filterSql.isEmpty() ? " WHERE " + filterSql : ""); 48 | return jdbcTemplate.query(orderSql, (orderRs, rowNum) -> { 49 | long orderId = orderRs.getLong("order_id"); 50 | 51 | Integer orderNumber = orderRs.getInt("order_number"); 52 | orderNumber = orderRs.wasNull() ? null : orderNumber; 53 | 54 | Date numberAssignDate = orderRs.getTimestamp("number_assign_date"); 55 | String formattedDate = numberAssignDate != null ? DATE_FORMAT.format(numberAssignDate) : null; 56 | 57 | List lineItems = itemDao.getDetailedLineItems(orderId); 58 | 59 | BigDecimal amountTendered = orderRs.getBigDecimal("tender.amount_tendered"); 60 | BigDecimal changeDue = orderRs.getBigDecimal("tender.change_due"); 61 | Tender tender = amountTendered != null && changeDue != null 62 | ? new Tender(null, orderId, amountTendered, changeDue) 63 | : null; 64 | 65 | BigDecimal totalDue = calculateTotalDue(lineItems); 66 | 67 | return new DetailedOrder(orderId, orderNumber, formattedDate, lineItems, totalDue, tender); 68 | }); 69 | } 70 | 71 | /** 72 | * @return the price of all of the items, including sales tax, or null if there aren't any items 73 | */ 74 | private BigDecimal calculateTotalDue(List lineItems) { 75 | if (lineItems.isEmpty()) 76 | return null; 77 | 78 | BigDecimal total = new BigDecimal(0); 79 | for (DetailedLineItem lineItem : lineItems) { 80 | total = total.add(lineItem.getExtendedPrice()); 81 | } 82 | 83 | BigDecimal tax = total.multiply(new BigDecimal(SettingsController.SALES_TAX_RATE)); 84 | return total.add(tax); 85 | } 86 | 87 | public long createOrder(Order order) { 88 | GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); 89 | namedParameterJdbcTemplate.update("INSERT INTO orders(order_number) VALUES (:orderNumber)", new BeanPropertySqlParameterSource(order), keyHolder); 90 | LOGGER.info("Created a new Order: " + order); 91 | return keyHolder.getKey().longValue(); 92 | } 93 | 94 | public int assignOrderNumber(long orderId) { 95 | Integer existingOrderNumber = jdbcTemplate.queryForObject("SELECT order_number FROM orders WHERE order_id = ?", Integer.class, orderId); 96 | if (existingOrderNumber != null) 97 | return existingOrderNumber; 98 | 99 | Integer newOrderNumber = determineNewOrderNumber(); 100 | jdbcTemplate.update("UPDATE orders SET order_number = ?, number_assign_date = CURRENT_TIMESTAMP WHERE order_id = ?", newOrderNumber, orderId); 101 | LOGGER.info("Assigned order number " + newOrderNumber + " to order " + orderId); 102 | return newOrderNumber; 103 | } 104 | 105 | /** 106 | * @return the most recently assigned order number, or 0 if there isn't one 107 | */ 108 | private int lookupMostRecentOrderNumber() { 109 | String mostRecentOrderNumberAssignDateSql = "SELECT MAX(B.number_assign_date) FROM orders AS B WHERE B.order_number IS NOT NULL"; 110 | try { 111 | return jdbcTemplate.queryForObject("SELECT order_number FROM orders AS A WHERE A.number_assign_date = (" + mostRecentOrderNumberAssignDateSql + ")", Integer.class); 112 | } catch (EmptyResultDataAccessException e) { 113 | // TODO: it would be nice to not have to rely on exceptions in normal operation 114 | return 0; 115 | } 116 | } 117 | 118 | /** 119 | * @return the new order number, resetting after we've reached 100 120 | */ 121 | private Integer determineNewOrderNumber() { 122 | int incrementedValue = lookupMostRecentOrderNumber() + 1; 123 | return (incrementedValue > 100 ? 1 : incrementedValue); 124 | } 125 | 126 | public void addItemToOrder(long orderId, long itemId) { 127 | Integer existingQuantity = countQuantityOfItemForOrder(orderId, itemId); 128 | if (existingQuantity == 0) 129 | createOrderLineItem(orderId, itemId); 130 | else 131 | updateOrderLineItem(orderId, itemId, existingQuantity + 1); 132 | LOGGER.info("Added item " + itemId + " to order " + orderId); 133 | } 134 | 135 | public void updateItemQuantity(long orderId, long itemId, int quantity) { 136 | jdbcTemplate.update("UPDATE order_line_items SET quantity = ? WHERE order_id = ? AND item_id = ?", quantity, orderId, itemId); 137 | } 138 | 139 | /** 140 | * Removes all occurrences of the item from the order 141 | */ 142 | public void deleteItemFromOrder(long orderId, long itemId) { 143 | jdbcTemplate.update("DELETE FROM order_line_items WHERE order_id = ? AND item_id = ?", orderId, itemId); 144 | LOGGER.info("Deleted all items with ID " + itemId + " from order " + orderId); 145 | } 146 | 147 | private int countQuantityOfItemForOrder(long orderId, long itemId) { 148 | try { 149 | return jdbcTemplate.queryForObject("SELECT quantity FROM order_line_items WHERE order_id = ? AND item_id = ?", Integer.class, orderId, itemId); 150 | } catch (EmptyResultDataAccessException e) { 151 | // TODO: it would be nice if catching exceptions wasn't part of normal operation... 152 | return 0; 153 | } 154 | } 155 | 156 | private void createOrderLineItem(long orderId, long itemId) { 157 | jdbcTemplate.update("INSERT INTO order_line_items(order_id, item_id, quantity) VALUES (?,?,1)", orderId, itemId); 158 | } 159 | 160 | private void updateOrderLineItem(long orderId, long itemId, int newQuantity) { 161 | jdbcTemplate.update("UPDATE order_line_items SET quantity = ? WHERE order_id = ? AND item_id = ?", newQuantity, orderId, itemId); 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/db/OrderFilter.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.db; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Jon Onstott 11 | * @since 11/3/2015 12 | */ 13 | public class OrderFilter { 14 | /** 15 | * If null, we won't filter based on the order number 16 | */ 17 | private final Boolean hasOrderNumber; 18 | /** 19 | * If null, we won't filter based on whether or not the order is tendered 20 | */ 21 | private final Boolean isTendered; 22 | 23 | @JsonCreator 24 | public OrderFilter(@JsonProperty("hasOrderNumber") Boolean hasOrderNumber, 25 | @JsonProperty("isTendered") Boolean isTendered) { 26 | this.hasOrderNumber = hasOrderNumber; 27 | this.isTendered = isTendered; 28 | } 29 | 30 | public Boolean getHasOrderNumber() { 31 | return hasOrderNumber; 32 | } 33 | 34 | public Boolean getIsTendered() { 35 | return isTendered; 36 | } 37 | 38 | public String getFilterSql() { 39 | List filters = new ArrayList<>(); 40 | 41 | if (isTendered != null) { 42 | String existsSubquery = "(SELECT * FROM tender WHERE tender.order_id = orders.order_id)"; 43 | if (isTendered) 44 | filters.add("EXISTS " + existsSubquery); 45 | else 46 | filters.add("NOT EXISTS " + existsSubquery); 47 | } 48 | 49 | if (hasOrderNumber != null) { 50 | if (hasOrderNumber) 51 | filters.add("order_number IS NOT NULL"); 52 | else 53 | filters.add("order_number IS NULL"); 54 | } 55 | 56 | return String.join(" AND ", filters); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/db/TenderDao.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.db; 2 | 3 | import com.reonsoftware.possample.models.Tender; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.jdbc.core.JdbcTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @author Jon Onstott 10 | * @since 11/3/2015 11 | */ 12 | @Component 13 | public class TenderDao { 14 | 15 | @Autowired 16 | private JdbcTemplate jdbcTemplate; 17 | 18 | public void createTenderRecord(Tender tender, long orderId) { 19 | jdbcTemplate.update("INSERT INTO tender(order_id, amount_tendered, change_due) VALUES (?,?,?)", 20 | orderId, 21 | tender.getAmountTendered(), 22 | tender.getChangeDue()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/models/DetailedLineItem.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.models; 2 | 3 | import java.math.BigDecimal; 4 | 5 | /** 6 | * Used when clients retrieve detailed Order data. 7 | */ 8 | public class DetailedLineItem { 9 | private final Item item; 10 | private final int quantity; 11 | private final BigDecimal extendedPrice; 12 | 13 | public DetailedLineItem(Item item, int quantity) { 14 | this.item = item; 15 | this.quantity = quantity; 16 | extendedPrice = item.getPrice().multiply(new BigDecimal(quantity)); 17 | } 18 | 19 | public Item getItem() { 20 | return item; 21 | } 22 | 23 | public int getQuantity() { 24 | return quantity; 25 | } 26 | 27 | public BigDecimal getExtendedPrice() { 28 | return extendedPrice; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/models/DetailedOrder.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.models; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.List; 5 | 6 | /** 7 | * An {@link Order}, populated with additional data that could be useful to clients. 8 | * Just used for order retrieval at the moment. 9 | */ 10 | public class DetailedOrder extends Order { 11 | private final List lineItems; 12 | private final BigDecimal totalDue; 13 | private final Tender tender; 14 | 15 | public DetailedOrder(long id, Integer orderNumber, String numberAssignDate, List lineItems, BigDecimal totalDue, Tender tender) { 16 | super(id, orderNumber, numberAssignDate); 17 | this.lineItems = lineItems; 18 | this.totalDue = totalDue; 19 | this.tender = tender; 20 | } 21 | 22 | public List getLineItems() { 23 | return lineItems; 24 | } 25 | 26 | public BigDecimal getTotalDue() { 27 | return totalDue; 28 | } 29 | 30 | public Tender getTender() { 31 | return tender; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/models/Item.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.math.BigDecimal; 7 | 8 | /** 9 | * @author Jon Onstott 10 | * @since 11/1/2015 11 | */ 12 | public class Item { 13 | 14 | /** 15 | * Can be null if this item hasn't been persisted yet 16 | */ 17 | private final Long id; 18 | private final String name; 19 | private final BigDecimal price; 20 | 21 | @JsonCreator 22 | public Item(@JsonProperty("id") Long id, 23 | @JsonProperty("name") String name, 24 | @JsonProperty("price") BigDecimal price) { 25 | this.id = id; 26 | this.name = name; 27 | this.price = price; 28 | } 29 | 30 | public Long getId() { 31 | return id; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public BigDecimal getPrice() { 39 | return price; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/models/Order.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author Jon Onstott 8 | * @since 11/1/2015 9 | */ 10 | public class Order { 11 | 12 | private final Long id; 13 | private final Integer orderNumber; 14 | /** 15 | * This is a formatted date & time 16 | */ 17 | private final String numberAssignDate; 18 | 19 | @JsonCreator 20 | public Order(@JsonProperty("id") Long id, 21 | @JsonProperty("orderNumber") Integer orderNumber, 22 | @JsonProperty("numberAssignDate") String numberAssignDate) { 23 | this.id = id; 24 | this.orderNumber = orderNumber; 25 | this.numberAssignDate = numberAssignDate; 26 | } 27 | 28 | public Long getId() { 29 | return id; 30 | } 31 | 32 | public Integer getOrderNumber() { 33 | return orderNumber; 34 | } 35 | 36 | public String getNumberAssignDate() { 37 | return numberAssignDate; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/models/Tender.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.math.BigDecimal; 6 | 7 | /** 8 | * @author Jon Onstott 9 | * @since 11/3/2015 10 | */ 11 | public class Tender { 12 | 13 | private final Long id; 14 | private final long orderId; 15 | private final BigDecimal amountTendered; 16 | private final BigDecimal changeDue; 17 | 18 | public Tender(@JsonProperty("id") Long id, 19 | @JsonProperty("orderId") long orderId, 20 | @JsonProperty("amountTendered") BigDecimal amountTendered, 21 | @JsonProperty("changeDue") BigDecimal changeDue) { 22 | this.id = id; 23 | this.orderId = orderId; 24 | this.amountTendered = amountTendered; 25 | this.changeDue = changeDue; 26 | } 27 | 28 | public Long getId() { 29 | return id; 30 | } 31 | 32 | public long getOrderId() { 33 | return orderId; 34 | } 35 | 36 | public BigDecimal getAmountTendered() { 37 | return amountTendered; 38 | } 39 | 40 | public BigDecimal getChangeDue() { 41 | return changeDue; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/rest/ItemController.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.rest; 2 | 3 | import com.reonsoftware.possample.db.ItemDao; 4 | import com.reonsoftware.possample.models.Item; 5 | import org.apache.commons.csv.CSVFormat; 6 | import org.apache.commons.csv.CSVParser; 7 | import org.apache.commons.csv.CSVRecord; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | import org.springframework.web.bind.annotation.RestController; 15 | import org.springframework.web.multipart.MultipartFile; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.InputStreamReader; 20 | import java.math.BigDecimal; 21 | import java.text.DecimalFormat; 22 | import java.text.DecimalFormatSymbols; 23 | import java.text.ParseException; 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | import java.util.stream.StreamSupport; 27 | 28 | /** 29 | * @author Jon Onstott 30 | * @since 11/1/2015 31 | */ 32 | @RestController 33 | public class ItemController { 34 | 35 | private static final Logger LOGGER = LoggerFactory.getLogger(ItemController.class); 36 | 37 | @Autowired 38 | private ItemDao itemDao; 39 | 40 | @RequestMapping("/api/items") 41 | public List getItems() { 42 | return itemDao.getItems(); 43 | } 44 | 45 | @RequestMapping(value = "/api/items/upload", method = RequestMethod.POST) 46 | public void handleItemListUpload(@RequestParam("file") MultipartFile file) { 47 | if (!file.isEmpty()) { 48 | try { 49 | List parsedItems = parseItemsCsv(file); 50 | parsedItems.forEach(itemDao::createItem); 51 | LOGGER.info("Imported " + parsedItems.size() + " items from a CSV file"); 52 | } catch (IOException e) { 53 | LOGGER.error("Failed to import items from a CSV file", e); 54 | } 55 | } else { 56 | LOGGER.warn("An empty items file was uploaded"); 57 | } 58 | } 59 | 60 | private List parseItemsCsv(MultipartFile file) throws IOException { 61 | try (InputStream inputStream = file.getInputStream()) { 62 | try (InputStreamReader reader = new InputStreamReader(inputStream)) { 63 | try (CSVParser csvParser = new CSVParser(reader, CSVFormat.EXCEL.withHeader().withIgnoreSurroundingSpaces().withIgnoreEmptyLines())) { 64 | Iterable records = csvParser.getRecords(); 65 | 66 | // Iterator to stream technique: http://stackoverflow.com/a/23936723/132374 67 | return StreamSupport.stream(records.spliterator(), false) 68 | .map(record -> { 69 | String name = null; 70 | String priceText = null; 71 | try { 72 | name = record.get("Item Name"); 73 | priceText = record.get("Price"); 74 | } catch (RuntimeException e) { 75 | String errorMessage = "Unable to process an item from a CSV file: " + record; 76 | LOGGER.error(errorMessage, e); 77 | throw new IllegalArgumentException(errorMessage, e); 78 | } 79 | return new Item(null, name, parseCurrency(priceText)); 80 | }) 81 | .collect(Collectors.toList()); 82 | } 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * From http://stackoverflow.com/a/18231943/132374 89 | */ 90 | private BigDecimal parseCurrency(String text) { 91 | DecimalFormatSymbols symbols = new DecimalFormatSymbols(); 92 | symbols.setGroupingSeparator(','); 93 | symbols.setDecimalSeparator('.'); 94 | 95 | DecimalFormat decimalFormat = new DecimalFormat("#,##0.0#", symbols); 96 | decimalFormat.setParseBigDecimal(true); 97 | try { 98 | return (BigDecimal) decimalFormat.parse(text); 99 | } catch (ParseException e) { 100 | throw new IllegalArgumentException("Unable to process the item's price: '" + text + "'"); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/rest/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.rest; 2 | 3 | import com.reonsoftware.possample.db.OrderDao; 4 | import com.reonsoftware.possample.db.OrderFilter; 5 | import com.reonsoftware.possample.models.DetailedOrder; 6 | import com.reonsoftware.possample.models.Order; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author Jon Onstott 15 | * @since 11/2/2015 16 | */ 17 | @RestController 18 | public class OrderController { 19 | 20 | @Autowired 21 | private OrderDao orderDao; 22 | 23 | @RequestMapping(value = "/api/orders", method = RequestMethod.GET) 24 | public List getOrders(@RequestParam(value = "hasOrderNumber", required = false) Boolean hasOrderNumber, 25 | @RequestParam(value = "isTendered", required = false) Boolean isTendered) { 26 | return orderDao.getOrders(new OrderFilter(hasOrderNumber, isTendered)); 27 | } 28 | 29 | /** 30 | * TODO: in production systems, a REST POST call would typically return the created object 31 | * 32 | * @return the PK of the created Order 33 | */ 34 | @RequestMapping(value = "/api/orders", method = RequestMethod.POST) 35 | @ResponseStatus(HttpStatus.CREATED) 36 | public long createOrder(@RequestBody Order order) { 37 | return orderDao.createOrder(order); 38 | } 39 | 40 | /** 41 | * @return the order's new order number 42 | */ 43 | @RequestMapping(value = "/api/orders/{orderId}/assignOrderNumber", method = RequestMethod.GET) 44 | public int assignOrderNumber(@PathVariable("orderId") long orderId) { 45 | return orderDao.assignOrderNumber(orderId); 46 | } 47 | 48 | @RequestMapping(value = "/api/orders/{orderId}/items", method = RequestMethod.POST) 49 | @ResponseStatus(HttpStatus.CREATED) 50 | public void addItemToOrder(@PathVariable("orderId") long orderId, 51 | @RequestParam("itemId") long itemId) { 52 | orderDao.addItemToOrder(orderId, itemId); 53 | } 54 | 55 | /** 56 | * Changes an item's quantity for an order. 57 | * 58 | * TODO: with this URL, you'd typically be updating the whole Item object 59 | */ 60 | @RequestMapping(value = "/api/orders/{orderId}/items/{itemId}", method = RequestMethod.PUT) 61 | @ResponseStatus(HttpStatus.OK) 62 | public void updateItemQuantity(@PathVariable("orderId") long orderId, 63 | @PathVariable("itemId") long itemId, 64 | @RequestParam("quantity") int quantity) { 65 | orderDao.updateItemQuantity(orderId, itemId, quantity); 66 | } 67 | 68 | /** 69 | * Removes the item from the order completely (no matter what the quantity is) 70 | */ 71 | @RequestMapping(value = "/api/orders/{orderId}/items/{itemId}", method = RequestMethod.DELETE) 72 | @ResponseStatus(HttpStatus.OK) 73 | public void deleteItemFromOrder(@PathVariable("orderId") long orderId, 74 | @PathVariable("itemId") long itemId) { 75 | orderDao.deleteItemFromOrder(orderId, itemId); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/rest/SettingsController.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.rest; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | /** 7 | * @author Jon Onstott 8 | * @since 11/3/2015 9 | */ 10 | @RestController 11 | public class SettingsController { 12 | 13 | /** 14 | * Sales tax, which is about 6%. This could be stored in the database or a configuration file. 15 | */ 16 | public static double SALES_TAX_RATE = 0.059446733372; 17 | 18 | @RequestMapping("/api/settings/salesTaxRate") 19 | public double getSalesTaxRate() { 20 | return SALES_TAX_RATE; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/reonsoftware/possample/rest/TenderController.java: -------------------------------------------------------------------------------- 1 | package com.reonsoftware.possample.rest; 2 | 3 | import com.reonsoftware.possample.db.TenderDao; 4 | import com.reonsoftware.possample.models.Tender; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | /** 10 | * @author Jon Onstott 11 | * @since 11/3/2015 12 | */ 13 | @RestController 14 | public class TenderController { 15 | 16 | @Autowired 17 | TenderDao tenderDao; 18 | 19 | @RequestMapping(value = "/api/orders/{orderId}/tender", method = RequestMethod.POST) 20 | @ResponseStatus(HttpStatus.CREATED) 21 | public void createTenderRecord(@PathVariable("orderId") long orderId, 22 | @RequestBody Tender tender) { 23 | tenderDao.createTenderRecord(tender, orderId); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/webapp/css/fileUpload.css: -------------------------------------------------------------------------------- 1 | #fileUploadPanel { 2 | margin-top: 40px; 3 | } 4 | 5 | /* The rest of this is from https://github.com/danialfarid/ng-file-upload */ 6 | 7 | .thumb { 8 | width: 24px; 9 | height: 24px; 10 | float: none; 11 | position: relative; 12 | top: 7px; 13 | } 14 | 15 | form .progress { 16 | line-height: 15px; 17 | } 18 | 19 | .progress { 20 | display: inline-block; 21 | width: 100px; 22 | border: 3px groove #CCC; 23 | } 24 | 25 | .progress div { 26 | font-size: smaller; 27 | background: orange; 28 | width: 0; 29 | } -------------------------------------------------------------------------------- /src/main/webapp/css/orderList.css: -------------------------------------------------------------------------------- 1 | #orderListButtonContainer { 2 | display: flex; 3 | } 4 | 5 | #orderListButtonSpacer { 6 | flex-grow: 1; 7 | } 8 | 9 | #newOrderButton { 10 | align-self: flex-end; 11 | } -------------------------------------------------------------------------------- /src/main/webapp/css/ordersSidebar.css: -------------------------------------------------------------------------------- 1 | #itemsAndSummaryContainer { 2 | display: flex; 3 | /* The 'view orders' button is float:right, above this div. So we need clear:both. */ 4 | clear: both; 5 | } 6 | 7 | #itemsList { 8 | display: flex; 9 | flex-grow: 1; 10 | flex-wrap: wrap; 11 | /* The buttons will be clumped together in the center of the div (though they have their own margin settings) */ 12 | justify-content: center; 13 | /* Make sure that the item buttons are along the top */ 14 | align-content: flex-start; 15 | } 16 | 17 | .itemButton { 18 | width: 100px; 19 | height: 100px; 20 | } 21 | 22 | #orderSidebar { 23 | display: flex; 24 | flex-direction: column; 25 | /* This is the width of the sidebar */ 26 | width: 300px; 27 | flex-shrink: 0; 28 | } 29 | 30 | #orderItemsPanel { 31 | display: flex; 32 | flex-direction: column; 33 | flex-grow: 1; 34 | /* This is the height of the sidebar */ 35 | min-height: 450px; 36 | } 37 | 38 | #orderItemsList { 39 | flex-grow: 1; 40 | } 41 | 42 | #totalsPanel { 43 | display: flex; 44 | flex-direction: column; 45 | } 46 | 47 | #paymentResultsPanel { 48 | padding-left: 20px; 49 | font-style: italic; 50 | font-weight: bold; 51 | color: green; 52 | } 53 | 54 | .totalsPanelRow { 55 | display: flex; 56 | padding: 4px 5px 4px 0; 57 | } 58 | 59 | .selectedRow { 60 | background-color: lightblue; 61 | } 62 | 63 | .orderNumber { 64 | margin: 0 5px; 65 | } 66 | 67 | .summaryLeftColumn { 68 | flex-grow: 1; 69 | } 70 | 71 | .summaryRightColumn { 72 | text-wrap: none; 73 | text-align: right; 74 | } 75 | 76 | #voidOrderButton { 77 | float: left; 78 | } 79 | 80 | #payNowButton { 81 | float: right; 82 | } -------------------------------------------------------------------------------- /src/main/webapp/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 15px; 3 | } 4 | 5 | .smallMargin { 6 | margin: 10px; 7 | } 8 | 9 | .smallHorizontalMargin { 10 | margin: 0 10px; 11 | } 12 | 13 | .smallVerticalMargin { 14 | margin: 10px 0; 15 | } 16 | 17 | .genericWarning { 18 | font-style: italic; 19 | font-weight: bold; 20 | } 21 | 22 | .centeredTable { 23 | margin-left: auto; 24 | margin-right: auto; 25 | } 26 | 27 | #viewOrdersButton { 28 | float: right; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/css/tenderPaymentDialog.css: -------------------------------------------------------------------------------- 1 | #paymentDialog { 2 | text-align: center; 3 | padding: 0 50px; 4 | } 5 | 6 | #paymentDialog td { 7 | padding: 3px; 8 | text-align: right; 9 | } 10 | 11 | .paymentDialogLeftColumn { 12 | text-align: right; 13 | } 14 | 15 | #txtAmountTendered { 16 | width: 50px; 17 | text-align: right; 18 | } -------------------------------------------------------------------------------- /src/main/webapp/fileUpload.html: -------------------------------------------------------------------------------- 1 |
4 | 5 |
6 |

Upload an items .csv file

7 |
8 | 9 |
10 |
11 | 18 | 19 |

20 | 21 |
{{f.name}} {{errFile.name}} {{errFile.$error}} {{errFile.$errorParam}} 22 | 23 |
24 |
25 |
26 | 27 | {{uploadMessage}} 28 |
29 |
30 |
-------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | POS Sample App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
38 | 39 |
40 |

Order entry

41 |
42 | 43 |
44 |
45 |
46 | 51 |
52 | 53 |
54 |
55 |
57 | Please use the .csv import feature below. 58 |
59 | 60 | 67 |
68 | 69 |
70 |
71 |
72 |
73 |
74 | 75 |
76 | 77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main/webapp/js/controllers/file-upload.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .controller('FileUploadController', FileUploadController); 5 | 6 | /** 7 | * FileUploadController controller 8 | * 9 | * Angular file upload code adapted from http://jsfiddle.net/danialfarid/0mz6ff9o/135/ 10 | */ 11 | FileUploadController.$inject = ['$scope', 'Upload', '$timeout']; 12 | function FileUploadController($scope, Upload, $timeout) { 13 | var vm = this; 14 | 15 | vm.uploadFiles = uploadFiles; 16 | 17 | function uploadFiles(file, errFiles) { 18 | $scope.f = file; 19 | $scope.errFile = errFiles && errFiles[0]; 20 | if (file) { 21 | file.upload = Upload.upload({ 22 | url: baseUrl + 'items/upload', 23 | data: {file: file} 24 | }); 25 | 26 | file.upload.then(function (response) { 27 | $timeout(function () { 28 | file.result = response.data; 29 | $scope.uploadMessage = 'Items imported successfully'; 30 | // TODO: it'd be better to make an items service that holds all items, and we'd be able to trigger a refresh of item data instead 31 | location.reload(); 32 | }); 33 | }, function (response) { 34 | // There was an error 35 | if (response.status > 0) 36 | $scope.uploadMessage = 'HTTP ' + response.status + ': ' + response.data; 37 | }, function (evt) { 38 | file.progress = Math.min(100, parseInt(100.0 * 39 | evt.loaded / evt.total)); 40 | }); 41 | } 42 | } 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /src/main/webapp/js/controllers/order-entry.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .controller('OrderEntryController', OrderEntryController); 5 | 6 | /** 7 | * OrderEntryController controller 8 | */ 9 | OrderEntryController.$inject = ['$http', '$uibModal', 'viewManager', 'orderStatus', 'orderPersistence']; 10 | function OrderEntryController($http, $uibModal, viewManager, orderStatus, orderPersistence) { 11 | var vm = this; 12 | 13 | vm.addItem = orderStatus.addItem; 14 | vm.allItems = []; 15 | vm.getGrandTotal = orderStatus.getGrandTotal; 16 | vm.getOrderNumber = orderStatus.getOrderNumber; 17 | vm.getSalesTax = orderStatus.getSalesTax; 18 | vm.getSubtotal = orderStatus.getSubtotal; 19 | vm.hasOrderNumber = orderStatus.hasOrderNumber; 20 | vm.isPaymentComplete = isPaymentComplete; 21 | vm.itemsInOrder = orderStatus.itemsInOrder; 22 | vm.paymentResults = orderStatus.paymentResults; 23 | vm.selectedItem = null; 24 | vm.selectItem = selectItem; 25 | vm.startPaying = startPaying; 26 | vm.viewManager = viewManager; 27 | vm.voidSelectedItem = voidSelectedItem; 28 | 29 | activate(); 30 | 31 | function activate() { 32 | // Load the list of menu items 33 | $http.get(baseUrl + 'items').success(function (data) { 34 | vm.allItems = data; 35 | }); 36 | } 37 | 38 | function startPaying() { 39 | // Assign the order number now that the user is ready to do payment 40 | orderPersistence.assignOrderNumber(orderStatus.getOrderId()) 41 | .then(function (response) { 42 | orderStatus.setOrderNumber(response.data); 43 | }); 44 | 45 | $uibModal.open({ 46 | animation: true, 47 | templateUrl: 'tenderPaymentDialog.html', 48 | controller: 'TenderPaymentController' 49 | }); 50 | } 51 | 52 | function isPaymentComplete() { 53 | return isFinite(vm.paymentResults.amountTendered) && isFinite(vm.paymentResults.changeDue); 54 | } 55 | 56 | function selectItem(item) { 57 | vm.selectedItem = item; 58 | } 59 | 60 | function voidSelectedItem() { 61 | orderStatus.removeItem(vm.selectedItem); 62 | vm.selectedItem = null; 63 | } 64 | } 65 | })(); 66 | -------------------------------------------------------------------------------- /src/main/webapp/js/controllers/order-list.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .controller('OrderListController', OrderListController); 5 | 6 | /** 7 | * OrderListController controller 8 | */ 9 | OrderListController.$inject = ['$scope', '$http', 'orderStatus', 'viewManager']; 10 | function OrderListController($scope, $http, orderStatus, viewManager) { 11 | var orderRetrievalPromise; 12 | var retrieveAfterPromiseCompletes = false; 13 | var vm = this; 14 | 15 | vm.allOrders = []; 16 | vm.areAllShown = areAllShown; 17 | vm.determineOrderStatus = determineOrderStatus; 18 | vm.hasOrderNumber = hasOrderNumber; 19 | vm.openOrder = openOrder; 20 | vm.ordersExist = ordersExist; 21 | vm.showInProgressOrders = false; 22 | vm.showPaidOrders = false; 23 | vm.showUnpaidOrders = false; 24 | vm.startNewOrder = startNewOrder; 25 | vm.toggleShowAll = toggleShowAll; 26 | vm.viewManager = viewManager; 27 | 28 | activate(); 29 | 30 | function activate() { 31 | // When the filters change, refresh the list of orders 32 | $scope.$watchGroup(['showInProgressOrders', 'showUnpaidOrders', 'showPaidOrders'], retrieveOrders); 33 | 34 | // When the orders list is shown, refresh the list of orders 35 | // From http://www.bennadel.com/blog/2658-using-scope-watch-to-watch-functions-in-angularjs.htm 36 | $scope.$watch(function () { 37 | return viewManager.isShowingList; 38 | }, function () { 39 | if (viewManager.isShowingList) { 40 | retrieveOrders(); 41 | } 42 | }); 43 | 44 | } 45 | 46 | function startNewOrder() { 47 | orderStatus.startNewOrder(); 48 | viewManager.setIsShowingList(false); 49 | } 50 | 51 | function openOrder(order) { 52 | orderStatus.editOrder(order); 53 | viewManager.setIsShowingList(false); 54 | } 55 | 56 | /** @returns {boolean} true if there could be orders to display */ 57 | function shouldLoadOrders() { 58 | return vm.showInProgressOrders || vm.showUnpaidOrders || vm.showPaidOrders; 59 | } 60 | 61 | function retrieveOrders() { 62 | if (!orderRetrievalPromise) { 63 | // We're not loading order data at the moment 64 | 65 | if (!shouldLoadOrders()) { 66 | vm.allOrders = []; 67 | retrieveAfterPromiseCompletes = false; 68 | return; 69 | } 70 | 71 | var filterOnOrderNumber = vm.showInProgressOrders !== vm.showUnpaidOrders; 72 | var filterOnTendered = vm.showUnpaidOrders !== vm.showPaidOrders; 73 | var hasOrderNumberParam = filterOnOrderNumber ? "hasOrderNumber=" + vm.showUnpaidOrders : ""; 74 | var isTenderedParam = filterOnTendered ? "isTendered=" + vm.showPaidOrders : ""; 75 | // TODO: remove '?' and '&' if we don't need them 76 | orderRetrievalPromise = $http.get(baseUrl + "orders?" + hasOrderNumberParam + "&" + isTenderedParam); 77 | orderRetrievalPromise.then(function (response) { 78 | orderRetrievalPromise = null; 79 | if (!retrieveAfterPromiseCompletes) { 80 | vm.allOrders = response.data; 81 | } else { 82 | // The toggle buttons have changed so we need to reload the order data 83 | retrieveAfterPromiseCompletes = false; 84 | retrieveOrders(); 85 | } 86 | }); 87 | } else { 88 | // We're in the process of loading order data already 89 | retrieveAfterPromiseCompletes = true; 90 | } 91 | } 92 | 93 | function ordersExist() { 94 | return vm.allOrders.length > 0; 95 | } 96 | 97 | function toggleShowAll() { 98 | var newState = !vm.areAllShown(); 99 | vm.showInProgressOrders = newState; 100 | vm.showUnpaidOrders = newState; 101 | vm.showPaidOrders = newState; 102 | } 103 | 104 | function areAllShown() { 105 | return vm.showInProgressOrders && vm.showUnpaidOrders && vm.showPaidOrders; 106 | } 107 | 108 | function determineOrderStatus(order) { 109 | var status = ""; 110 | if (!order.orderNumber) { 111 | status += "IN PROGRESS "; 112 | } 113 | if (order.tender) { 114 | status += "PAID "; 115 | } else { 116 | status += "UNPAID"; 117 | } 118 | return status; 119 | } 120 | 121 | function hasOrderNumber(order) { 122 | return order.orderNumber && isFinite(order.orderNumber); 123 | } 124 | } 125 | })(); 126 | -------------------------------------------------------------------------------- /src/main/webapp/js/controllers/tender-payment.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .controller('TenderPaymentController', TenderPaymentController); 5 | 6 | /** 7 | * TenderPaymentController controller 8 | */ 9 | TenderPaymentController.$inject = ['$uibModalInstance', 'orderStatus', 'orderPersistence']; 10 | function TenderPaymentController($uibModalInstance, orderStatus, orderPersistence) { 11 | var vm = this; 12 | 13 | vm.amountTendered = 0; 14 | vm.cancelPayment = cancelPayment; 15 | vm.getChangeDue = getChangeDue; 16 | vm.getGrandTotal = orderStatus.getGrandTotal; 17 | vm.isPaymentValid = isPaymentValid; 18 | vm.submitPayment = submitPayment; 19 | 20 | function getChangeDue() { 21 | var amountTendered = vm.amountTendered; 22 | if (amountTendered && isFinite(amountTendered)) { 23 | var change = amountTendered - orderStatus.getGrandTotal(); 24 | if (change > 0) { 25 | return change; 26 | } 27 | } 28 | return "-----"; 29 | } 30 | 31 | function isPaymentValid() { 32 | return isFinite(getChangeDue()); 33 | } 34 | 35 | function submitPayment() { 36 | var amountTendered = vm.amountTendered; 37 | var changeDue = getChangeDue(); 38 | 39 | orderStatus.paymentResults.amountTendered = amountTendered; 40 | orderStatus.paymentResults.changeDue = changeDue; 41 | 42 | orderPersistence.recordPayment(orderStatus.getOrderId(), amountTendered, changeDue); 43 | 44 | $uibModalInstance.close(); 45 | } 46 | 47 | function cancelPayment() { 48 | $uibModalInstance.dismiss('cancel'); 49 | } 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-animate/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular-animate 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAnimate). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular-animate 15 | ``` 16 | 17 | Then add `ngAnimate` as a dependency for your app: 18 | 19 | ```javascript 20 | angular.module('myApp', [require('angular-animate')]); 21 | ``` 22 | 23 | ### bower 24 | 25 | ```shell 26 | bower install angular-animate 27 | ``` 28 | 29 | Then add a ` 33 | ``` 34 | 35 | Then add `ngAnimate` as a dependency for your app: 36 | 37 | ```javascript 38 | angular.module('myApp', ['ngAnimate']); 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Documentation is available on the 44 | [AngularJS docs site](http://docs.angularjs.org/api/ngAnimate). 45 | 46 | ## License 47 | 48 | The MIT License 49 | 50 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-animate/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-animate", 3 | "version": "1.4.7", 4 | "main": "./angular-animate.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.7" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-animate/index.js: -------------------------------------------------------------------------------- 1 | require('./angular-animate'); 2 | module.exports = 'ngAnimate'; 3 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-animate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-animate", 3 | "version": "1.4.7", 4 | "description": "AngularJS module for animations", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "animation", 18 | "client-side" 19 | ], 20 | "author": { 21 | "name": "Angular Core Team", 22 | "email": "angular-core+npm@google.com" 23 | }, 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/angular/angular.js/issues" 27 | }, 28 | "homepage": "http://angularjs.org", 29 | "gitHead": "3e63136fc3d882828594f3ceb929784eb43aa944", 30 | "_id": "angular-animate@1.4.7", 31 | "_shasum": "4399e6dd39ca4cb51ccd58a19e09c3eaae0eeb8a", 32 | "_from": "angular-animate@*", 33 | "_npmVersion": "2.0.2", 34 | "_nodeVersion": "0.10.26", 35 | "_npmUser": { 36 | "name": "angularcore", 37 | "email": "angular-core+npm@google.com" 38 | }, 39 | "maintainers": [ 40 | { 41 | "name": "akalinovski", 42 | "email": "a.kalinovski@gmail.com" 43 | }, 44 | { 45 | "name": "ws-malysheva", 46 | "email": "ws.malysheva@gmail.com" 47 | }, 48 | { 49 | "name": "pitbeast", 50 | "email": "PitBeast777@gmail.com" 51 | }, 52 | { 53 | "name": "angularcore", 54 | "email": "angular-core+npm@google.com" 55 | }, 56 | { 57 | "name": "petebd", 58 | "email": "pete@bacondarwin.com" 59 | } 60 | ], 61 | "dist": { 62 | "shasum": "4399e6dd39ca4cb51ccd58a19e09c3eaae0eeb8a", 63 | "tarball": "http://registry.npmjs.org/angular-animate/-/angular-animate-1.4.7.tgz" 64 | }, 65 | "directories": {}, 66 | "_resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.4.7.tgz" 67 | } 68 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-ui-bootstrap/.npmignore: -------------------------------------------------------------------------------- 1 | bower.json -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-ui-bootstrap/README.md: -------------------------------------------------------------------------------- 1 | ### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com) 2 | 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.svg)](http://travis-ci.org/angular-ui/bootstrap) 5 | [![devDependency Status](https://david-dm.org/angular-ui/bootstrap/dev-status.svg?branch=master)](https://david-dm.org/angular-ui/bootstrap#info=devDependencies) 6 | 7 | ### Quick links 8 | - [Demo](#demo) 9 | - [Installation](#installation) 10 | - [NPM](#install-with-npm) 11 | - [Bower](#install-with-bower) 12 | - [NuGet](#install-with-nuget) 13 | - [Custom](#custom-build) 14 | - [Manual](#manual-download) 15 | - [Support](#support) 16 | - [FAQ](#faq) 17 | - [Supported browsers](#supported-browsers) 18 | - [Need help?](#need-help) 19 | - [Found a bug?](#found-a-bug) 20 | - [Contributing to the project](#contributing-to-the-project) 21 | - [Development, meeting minutes, roadmap and more.](#development-meeting-minutes-roadmap-and-more) 22 | 23 | 24 | # Demo 25 | 26 | Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/! 27 | 28 | # Installation 29 | 30 | Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required. 31 | Note: Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation. 32 | 33 | #### Install with NPM 34 | 35 | ```sh 36 | $ npm install angular-ui-bootstrap 37 | ``` 38 | 39 | This will install AngularJS and Bootstrap NPM packages. 40 | 41 | #### Install with Bower 42 | ```sh 43 | $ bower install angular-bootstrap 44 | ``` 45 | 46 | Note: do not install 'angular-ui-bootstrap'. A separate repository - [bootstrap-bower](https://github.com/angular-ui/bootstrap-bower) - hosts the compiled javascript file and bower.json. 47 | 48 | #### Install with NuGet 49 | To install AngularJS UI Bootstrap, run the following command in the Package Manager Console 50 | 51 | ```sh 52 | PM> Install-Package Angular.UI.Bootstrap 53 | ``` 54 | 55 | #### Custom build 56 | 57 | Head over to http://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it. 58 | 59 | #### Manual download 60 | 61 | After downloading dependencies (or better yet, referencing them from your favorite CDN) you need to download build version of this project. All the files and their purposes are described here: 62 | https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files 63 | Don't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`. 64 | 65 | ### Adding dependency to your project 66 | 67 | When you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module: 68 | 69 | ```js 70 | angular.module('myModule', ['ui.bootstrap']); 71 | ``` 72 | 73 | If you're a Browserify or Webpack user, you can do: 74 | 75 | ```js 76 | var uibs = require('angular-ui-bootstrap'); 77 | 78 | angular.module('myModule', [uibs]); 79 | ``` 80 | 81 | # Support 82 | 83 | ## FAQ 84 | 85 | https://github.com/angular-ui/bootstrap/wiki/FAQ 86 | 87 | ## Supported browsers 88 | 89 | Directives from this repository are automatically tested with the following browsers: 90 | * Chrome (stable and canary channel) 91 | * Firefox 92 | * IE 9 and 10 93 | * Opera 94 | * Safari 95 | 96 | Modern mobile browsers should work without problems. 97 | 98 | 99 | ## Need help? 100 | Need help using UI Bootstrap? 101 | 102 | * Live help in the IRC (`#angularjs` channel at the `freenode` network). Use this [webchat](https://webchat.freenode.net/) or your own IRC client. 103 | * Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag. 104 | 105 | **Please do not create new issues in this repository to ask questions about using UI Bootstrap** 106 | 107 | ## Found a bug? 108 | Please take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug) and submit your issue [here](https://github.com/angular-ui/bootstrap/issues/new). 109 | 110 | 111 | ---- 112 | 113 | 114 | # Contributing to the project 115 | 116 | We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines. 117 | 118 | # Development, meeting minutes, roadmap and more. 119 | 120 | Head over to the [Wiki](https://github.com/angular-ui/bootstrap/wiki) for notes on development for UI Bootstrap, meeting minutes from the UI Bootstrap team, roadmap plans, project philosophy and more. 121 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-ui-bootstrap/index.js: -------------------------------------------------------------------------------- 1 | require('./ui-bootstrap-tpls'); 2 | module.exports = 'ui.bootstrap'; 3 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-ui-bootstrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-bootstrap", 3 | "version": "0.14.3", 4 | "description": "Bootstrap widgets for Angular", 5 | "main": "index.js", 6 | "homepage": "http://angular-ui.github.io/bootstrap/", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/angular-ui/bootstrap.git" 10 | }, 11 | "keywords": [ 12 | "angular", 13 | "bootstrap", 14 | "angular-ui", 15 | "components", 16 | "client-side" 17 | ], 18 | "author": { 19 | "name": "https://github.com/angular-ui/bootstrap/graphs/contributors" 20 | }, 21 | "peerDependencies": { 22 | "angular": "^1.3.x || >= 1.4.0-beta.0 || >= 1.5.0-beta.0" 23 | }, 24 | "license": "MIT", 25 | "gitHead": "b5e11f5861a1591a300e78a9902c1a7e7918d75b", 26 | "bugs": { 27 | "url": "https://github.com/angular-ui/bootstrap/issues" 28 | }, 29 | "_id": "angular-ui-bootstrap@0.14.3", 30 | "scripts": {}, 31 | "_shasum": "d96ce604c84bbd706ba34aabca1d5a7376e18ba6", 32 | "_from": "angular-ui-bootstrap@*", 33 | "_npmVersion": "2.12.1", 34 | "_nodeVersion": "0.10.40", 35 | "_npmUser": { 36 | "name": "wesleycho", 37 | "email": "wesley.cho@gmail.com" 38 | }, 39 | "dist": { 40 | "shasum": "d96ce604c84bbd706ba34aabca1d5a7376e18ba6", 41 | "tarball": "http://registry.npmjs.org/angular-ui-bootstrap/-/angular-ui-bootstrap-0.14.3.tgz" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "goldcaddy77", 46 | "email": "goldcaddy77@gmail.com" 47 | }, 48 | { 49 | "name": "foxandxss", 50 | "email": "Foxandxss@gmail.com" 51 | }, 52 | { 53 | "name": "wesleycho", 54 | "email": "wesley.cho@gmail.com" 55 | } 56 | ], 57 | "directories": {}, 58 | "_resolved": "https://registry.npmjs.org/angular-ui-bootstrap/-/angular-ui-bootstrap-0.14.3.tgz", 59 | "readme": "ERROR: No README data found!" 60 | } 61 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular-ui-bootstrap/ui-bootstrap-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | .ng-animate.item:not(.left):not(.right) { 4 | -webkit-transition: 0s ease-in-out left; 5 | transition: 0s ease-in-out left 6 | } -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular 15 | ``` 16 | 17 | Then add a ` 21 | ``` 22 | 23 | Or `require('angular')` from your code. 24 | 25 | ### bower 26 | 27 | ```shell 28 | bower install angular 29 | ``` 30 | 31 | Then add a ` 35 | ``` 36 | 37 | ## Documentation 38 | 39 | Documentation is available on the 40 | [AngularJS docs site](http://docs.angularjs.org/). 41 | 42 | ## License 43 | 44 | The MIT License 45 | 46 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide:not(.ng-hide-animate) { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-shim { 16 | visibility:hidden; 17 | } 18 | 19 | .ng-anchor { 20 | position:absolute; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular/angular.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/src/main/webapp/js/node_modules/angular/angular.min.js.gzip -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.4.7", 4 | "main": "./angular.js", 5 | "ignore": [], 6 | "dependencies": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular/index.js: -------------------------------------------------------------------------------- 1 | require('./angular'); 2 | module.exports = angular; 3 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.4.7", 4 | "description": "HTML enhanced for web apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "client-side" 18 | ], 19 | "author": { 20 | "name": "Angular Core Team", 21 | "email": "angular-core+npm@google.com" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/angular/angular.js/issues" 26 | }, 27 | "homepage": "http://angularjs.org", 28 | "gitHead": "6bdc6b4855b416bf029105324080ca7d6aca0e9f", 29 | "_id": "angular@1.4.7", 30 | "_shasum": "f8990e71c9cd180a842b07e09e0801fe84e83878", 31 | "_from": "angular@*", 32 | "_npmVersion": "2.0.2", 33 | "_nodeVersion": "0.10.26", 34 | "_npmUser": { 35 | "name": "angularcore", 36 | "email": "angular-core+npm@google.com" 37 | }, 38 | "maintainers": [ 39 | { 40 | "name": "bclinkinbeard", 41 | "email": "ben.clinkinbeard@gmail.com" 42 | }, 43 | { 44 | "name": "angularcore", 45 | "email": "angular-core+npm@google.com" 46 | }, 47 | { 48 | "name": "petebd", 49 | "email": "pete@bacondarwin.com" 50 | } 51 | ], 52 | "dist": { 53 | "shasum": "f8990e71c9cd180a842b07e09e0801fe84e83878", 54 | "tarball": "http://registry.npmjs.org/angular/-/angular-1.4.7.tgz" 55 | }, 56 | "directories": {}, 57 | "_resolved": "https://registry.npmjs.org/angular/-/angular-1.4.7.tgz" 58 | } 59 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "camelcase": true, 6 | "curly": false, 7 | "eqeqeq": true, 8 | "eqnull": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | "unused": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "jquery": true, 20 | "evil": true, 21 | "globals": { 22 | "angular":false, 23 | "FileAPI":false, 24 | "ngFileUpload":true, 25 | "FormData":true, 26 | "Blob":true, 27 | "ActiveXObject":false, 28 | "$document":false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | bower_components 4 | .settings 5 | .metadata 6 | *.war 7 | RemoteSystemsTempFiles/ 8 | .DS_Store 9 | .DS_Store? 10 | ehthumbs.db 11 | Thumbs.db 12 | .Spotlight-V100 13 | .Trashes 14 | classes/ 15 | ._* 16 | *.jar 17 | release-local.sh 18 | npm-debug.log 19 | .idea/ 20 | target/ 21 | *.iml 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | // Load grunt tasks automatically 5 | require('load-grunt-tasks')(grunt); 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | concat: { 10 | all: { 11 | options: { 12 | process: function (content) { 13 | return grunt.template.process(content); 14 | } 15 | }, 16 | files: { 17 | 'dist/ng-file-upload.js': ['src/upload.js', 'src/model.js', 'src/select.js', 'src/data-url.js', 18 | 'src/validate.js', 'src/resize.js', 'src/drop.js', 'src/exif.js'], 19 | 'dist/ng-file-upload-shim.js': ['src/shim-upload.js', 'src/shim-elem.js', 'src/shim-filereader.js'], 20 | 'dist/ng-file-upload-all.js': ['dist/ng-file-upload-shim.js', 'dist/ng-file-upload.js'] 21 | } 22 | } 23 | }, 24 | uglify: { 25 | options: { 26 | preserveComments: 'some', 27 | banner: '/*! <%= pkg.version %> */\n' 28 | }, 29 | 30 | build: { 31 | files: [{ 32 | 'dist/ng-file-upload.min.js': 'dist/ng-file-upload.js', 33 | 'dist/ng-file-upload-shim.min.js': 'dist/ng-file-upload-shim.js', 34 | 'dist/ng-file-upload-all.min.js': 'dist/ng-file-upload-all.js', 35 | 'dist/FileAPI.min.js': 'dist/FileAPI.js' 36 | }] 37 | } 38 | }, 39 | copy: { 40 | build: { 41 | files: [{ 42 | expand: true, 43 | cwd: 'dist/', 44 | src: '*', 45 | dest: 'demo/src/main/webapp/js/', 46 | flatten: true, 47 | filter: 'isFile' 48 | }] 49 | }, 50 | fileapi: { 51 | files: { 52 | 'dist/FileAPI.flash.swf': 'src/FileAPI.flash.swf', 53 | 'dist/FileAPI.js': 'src/FileAPI.js' 54 | } 55 | }, 56 | bower: { 57 | files: [{ 58 | expand: true, 59 | cwd: 'dist/', 60 | src: '*', 61 | dest: '../angular-file-upload-bower/', 62 | flatten: true, 63 | filter: 'isFile' 64 | }, { 65 | expand: true, 66 | cwd: 'dist/', 67 | src: '*', 68 | dest: '../angular-file-upload-shim-bower/', 69 | flatten: true, 70 | filter: 'isFile' 71 | }] 72 | } 73 | }, 74 | serve: { 75 | options: { 76 | port: 9000 77 | }, 78 | 'path': 'demo/src/main/webapp' 79 | }, 80 | watch: { 81 | js: { 82 | files: ['src/{,*/}*.js'], 83 | tasks: ['jshint:all', 'concat:all', 'copy:build'] 84 | } 85 | }, 86 | jshint: { 87 | options: { 88 | jshintrc: '.jshintrc', 89 | reporter: require('jshint-stylish') 90 | }, 91 | all: [ 92 | 'Gruntfile.js', 93 | 'src/{,*/}*.js', 94 | '!src/FileAPI*.*', 95 | 'test/spec/{,*/}*.js' 96 | ] 97 | }, 98 | replace: { 99 | version: { 100 | src: ['nuget/Package.nuspec'], 101 | overwrite: true, 102 | replacements: [{ 103 | from: /"version" *: *".*"/g, 104 | to: '"version": "<%= pkg.version %>"' 105 | }, { 106 | from: /.*<\/version>/g, 107 | to: '<%= pkg.version %>' 108 | }] 109 | } 110 | }, 111 | clean: { 112 | dist: { 113 | files: [{ 114 | dot: true, 115 | src: [ 116 | 'dist', 117 | '!dist/.git*' 118 | ] 119 | }] 120 | } 121 | } 122 | }); 123 | 124 | grunt.registerTask('dev', ['jshint:all', 'concat:all', 'uglify', 'copy:build', 'watch']); 125 | grunt.registerTask('default', ['jshint:all', 'clean:dist', 'concat:all', 126 | 'copy:fileapi', 'uglify', 'copy:build', 'copy:bower', 'replace:version']); 127 | 128 | }; 129 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 danialfarid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/.name: -------------------------------------------------------------------------------- 1 | ng-file-upload-demo-server -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/artifacts/ng_file_upload_demo_server_war.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/target 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/artifacts/ng_file_upload_demo_server_war_exploded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/target/ng-file-upload-demo-server-0.0.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 33 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/libraries/Maven__com_google_appengine_appengine_api_1_0_sdk_1_9_18.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/libraries/Maven__com_google_appengine_appengine_endpoints_1_9_18.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/libraries/Maven__commons_fileupload_commons_fileupload_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/libraries/Maven__javax_inject_javax_inject_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/libraries/Maven__javax_servlet_servlet_api_2_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 1.7 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/C#/UploadController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .controller('UploadCtrl', UploadCtrl); 7 | 8 | UploadCtrl.$inject = ['$location', '$upload']; 9 | 10 | function UploadCtrl($location, $upload) { 11 | /* jshint validthis:true */ 12 | var vm = this; 13 | vm.title = 'UploadCtrl'; 14 | 15 | vm.onFileSelect = function ($files, user) { 16 | //$files: an array of files selected, each file has name, size, and type. 17 | for (var i = 0; i < $files.length; i++) { 18 | var file = $files[i]; 19 | vm.upload = $upload.upload({ 20 | url: 'Uploads/UploadHandler.ashx', 21 | data: { name: user.Name }, 22 | file: file, // or list of files ($files) for html5 only 23 | }).progress(function (evt) { 24 | //console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total)); 25 | }).success(function (data, status, headers, config) { 26 | alert('Uploaded successfully ' + file.name); 27 | }).error(function (err) { 28 | alert('Error occured during upload'); 29 | }); 30 | } 31 | }; 32 | 33 | } 34 | })(); 35 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/C#/UploadHandler.ashx: -------------------------------------------------------------------------------- 1 | <%@ WebHandler Language="C#" CodeBehind="UploadHandler.ashx.cs" Class="MyApp.Uploads.UploadHandler" %> 2 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/C#/UploadHandler.ashx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace MyApp.Uploads 7 | { 8 | /// 9 | /// Summary description for UploadHandler 10 | /// 11 | public class UploadHandler : IHttpHandler 12 | { 13 | 14 | public void ProcessRequest(HttpContext context) 15 | { 16 | if (context.Request.Files.Count > 0) 17 | { 18 | HttpFileCollection files = context.Request.Files; 19 | var userName = context.Request.Form["name"]; 20 | for (int i = 0; i < files.Count; i++) 21 | { 22 | HttpPostedFile file = files[i]; 23 | 24 | string fname = context.Server.MapPath("Uploads\\" + userName.ToUpper() + "\\" + file.FileName); 25 | file.SaveAs(fname); 26 | } 27 | } 28 | context.Response.ContentType = "text/plain"; 29 | context.Response.Write("File/s uploaded successfully!"); 30 | } 31 | 32 | public bool IsReusable 33 | { 34 | get 35 | { 36 | return false; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | war 6 | 0.0.1 7 | ng-file-upload-demo-server 8 | com.df.ng-file-upload.demo.server 9 | 10 | 11 | 1 12 | UTF-8 13 | 14 | 15 | 16 | 3.1.0 17 | 18 | 19 | 20 | 21 | commons-fileupload 22 | commons-fileupload 23 | 1.2 24 | 25 | 26 | 27 | 28 | com.google.appengine 29 | appengine-api-1.0-sdk 30 | 1.9.18 31 | 32 | 33 | com.google.appengine 34 | appengine-endpoints 35 | 1.9.18 36 | 37 | 38 | javax.servlet 39 | servlet-api 40 | 2.5 41 | provided 42 | 43 | 44 | javax.inject 45 | javax.inject 46 | 1 47 | 48 | 49 | 50 | 51 | 52 | ${project.build.directory}/${project.build.finalName}/WEB-INF/classes 53 | 54 | 55 | org.apache.maven.plugins 56 | 3.2 57 | maven-compiler-plugin 58 | 59 | 1.7 60 | 1.7 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-war-plugin 66 | 2.6 67 | 68 | true 69 | 70 | <!– in order to interpolate version from pom into appengine-web.xml –> 71 | 72 | ${basedir}/src/main/webapp/WEB-INF 73 | true 74 | WEB-INF 75 | 76 | 77 | 78 | 79 | 80 | 81 | com.google.appengine 82 | appengine-maven-plugin 83 | 1.9.18 84 | 85 | false 86 | 87 |
0.0.0.0
88 | 8888 89 | 1 90 | 91 | -Xdebug 92 | -Xrunjdwp:transport=dt_socket,address=1044,server=y,suspend=n 93 | 94 |
95 |
96 |
97 |
98 |
99 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/java/com/df/angularfileupload/CORSFilter.java: -------------------------------------------------------------------------------- 1 | package com.df.angularfileupload; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.Filter; 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.FilterConfig; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.ServletRequest; 10 | import javax.servlet.ServletResponse; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | public class CORSFilter implements Filter { 15 | 16 | @Override 17 | public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, 18 | ServletException { 19 | HttpServletResponse httpResp = (HttpServletResponse) resp; 20 | HttpServletRequest httpReq = (HttpServletRequest) req; 21 | 22 | httpResp.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS"); 23 | httpResp.setHeader("Access-Control-Allow-Origin", "*"); 24 | if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) { 25 | httpResp.setHeader("Access-Control-Allow-Headers", 26 | httpReq.getHeader("Access-Control-Request-Headers")); 27 | } 28 | chain.doFilter(req, resp); 29 | } 30 | 31 | @Override 32 | public void init(FilterConfig arg0) throws ServletException { 33 | } 34 | 35 | @Override 36 | public void destroy() { 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/java/com/df/angularfileupload/FileUpload.java: -------------------------------------------------------------------------------- 1 | package com.df.angularfileupload; 2 | 3 | import com.google.appengine.repackaged.org.joda.time.LocalDateTime; 4 | import org.apache.commons.fileupload.FileItemIterator; 5 | import org.apache.commons.fileupload.FileItemStream; 6 | import org.apache.commons.fileupload.servlet.ServletFileUpload; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.BufferedReader; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | import java.util.Enumeration; 17 | import java.util.Map; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.logging.Logger; 20 | 21 | public class FileUpload extends HttpServlet { 22 | private static final long serialVersionUID = -8244073279641189889L; 23 | private final Logger log = Logger.getLogger(FileUpload.class.getName()); 24 | 25 | class SizeEntry { 26 | public int size; 27 | public LocalDateTime time; 28 | } 29 | static Map sizeMap = new ConcurrentHashMap<>(); 30 | int counter; 31 | 32 | @Override 33 | protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { 34 | try { 35 | clearOldValuesInSizeMap(); 36 | 37 | String ipAddress = req.getHeader("X-FORWARDED-FOR"); 38 | if (ipAddress == null) { 39 | ipAddress = req.getRemoteAddr(); 40 | } 41 | if (req.getMethod().equalsIgnoreCase("GET")) { 42 | if (req.getParameter("restart") != null) { 43 | sizeMap.remove(ipAddress + req.getParameter("name")); 44 | } 45 | SizeEntry entry = sizeMap.get(ipAddress + req.getParameter("name")); 46 | res.getWriter().write("{\"size\":" + (entry == null ? 0 : entry.size) + "}"); 47 | res.setContentType("application/json"); 48 | return; 49 | } 50 | req.setCharacterEncoding("utf-8"); 51 | if (!"OPTIONS".equalsIgnoreCase(req.getMethod()) && req.getParameter("errorCode") != null) { 52 | // res.getWriter().write(req.getParameter("errorMessage")); 53 | // res.getWriter().flush(); 54 | res.sendError(Integer.parseInt(req.getParameter("errorCode")), req.getParameter("errorMessage")); 55 | return; 56 | } 57 | StringBuilder sb = new StringBuilder("{\"result\": ["); 58 | 59 | if (req.getHeader("Content-Type") != null 60 | && req.getHeader("Content-Type").startsWith("multipart/form-data")) { 61 | ServletFileUpload upload = new ServletFileUpload(); 62 | 63 | FileItemIterator iterator = upload.getItemIterator(req); 64 | 65 | while (iterator.hasNext()) { 66 | FileItemStream item = iterator.next(); 67 | sb.append("{"); 68 | sb.append("\"fieldName\":\"").append(item.getFieldName()).append("\","); 69 | if (item.getName() != null) { 70 | sb.append("\"name\":\"").append(item.getName()).append("\","); 71 | } 72 | if (item.getName() != null) { 73 | sb.append("\"size\":\"").append(size(ipAddress + item.getName(), item.openStream())).append("\""); 74 | } else { 75 | sb.append("\"value\":\"").append(read(item.openStream()).replace("\"", "'")).append("\""); 76 | } 77 | sb.append("}"); 78 | if (iterator.hasNext()) { 79 | sb.append(","); 80 | } 81 | } 82 | } else { 83 | sb.append("{\"size\":\"" + size(ipAddress, req.getInputStream()) + "\"}"); 84 | } 85 | 86 | sb.append("]"); 87 | sb.append(", \"requestHeaders\": {"); 88 | @SuppressWarnings("unchecked") 89 | Enumeration headerNames = req.getHeaderNames(); 90 | while (headerNames.hasMoreElements()) { 91 | String header = headerNames.nextElement(); 92 | sb.append("\"").append(header).append("\":\"").append(req.getHeader(header)).append("\""); 93 | if (headerNames.hasMoreElements()) { 94 | sb.append(","); 95 | } 96 | } 97 | sb.append("}}"); 98 | res.setCharacterEncoding("utf-8"); 99 | res.getWriter().write(sb.toString()); 100 | } catch (Exception ex) { 101 | throw new ServletException(ex); 102 | } 103 | } 104 | 105 | private void clearOldValuesInSizeMap() { 106 | if (counter++ == 100) { 107 | for (Map.Entry entry : sizeMap.entrySet()) { 108 | if (entry.getValue().time.isBefore(LocalDateTime.now().minusHours(1))) { 109 | sizeMap.remove(entry.getKey()); 110 | } 111 | } 112 | counter = 0; 113 | } 114 | } 115 | 116 | protected int size(String key, InputStream stream) { 117 | int length = sizeMap.get(key) == null ? 0 : sizeMap.get(key).size; 118 | try { 119 | byte[] buffer = new byte[200000]; 120 | int size; 121 | while ((size = stream.read(buffer)) != -1) { 122 | length += size; 123 | SizeEntry entry = new SizeEntry(); 124 | entry.size = length; 125 | entry.time = LocalDateTime.now(); 126 | sizeMap.put(key, entry); 127 | // for (int i = 0; i < size; i++) { 128 | // System.out.print((char) buffer[i]); 129 | // } 130 | } 131 | } catch (IOException e) { 132 | throw new RuntimeException(e); 133 | } 134 | System.out.println(length); 135 | return length; 136 | 137 | } 138 | 139 | protected String read(InputStream stream) { 140 | StringBuilder sb = new StringBuilder(); 141 | BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); 142 | try { 143 | String line; 144 | while ((line = reader.readLine()) != null) { 145 | sb.append(line); 146 | } 147 | } catch (IOException e) { 148 | throw new RuntimeException(e); 149 | } finally { 150 | try { 151 | reader.close(); 152 | } catch (IOException e) { 153 | //ignore 154 | } 155 | } 156 | return sb.toString(); 157 | } 158 | } -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/java/com/df/angularfileupload/S3Signature.java: -------------------------------------------------------------------------------- 1 | package com.df.angularfileupload; 2 | 3 | import com.google.api.server.spi.IoUtil; 4 | import com.google.appengine.repackaged.com.google.common.io.BaseEncoding; 5 | 6 | import javax.crypto.Mac; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.security.InvalidKeyException; 14 | import java.security.NoSuchAlgorithmException; 15 | 16 | public class S3Signature extends HttpServlet { 17 | @Override 18 | protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { 19 | String policy_document = IoUtil.readStream(req.getInputStream()); 20 | System.out.println(policy_document); 21 | String policy = BaseEncoding.base64().encode(policy_document.getBytes("UTF-8")). 22 | replaceAll("\n","").replaceAll("\r",""); 23 | 24 | Mac hmac; 25 | try { 26 | hmac = Mac.getInstance("HmacSHA1"); 27 | String aws_secret_key = req.getParameter("aws-secret-key"); 28 | System.out.println(aws_secret_key); 29 | 30 | hmac.init(new SecretKeySpec(aws_secret_key.getBytes("UTF-8"), "HmacSHA1")); 31 | String signature = BaseEncoding.base64().encode( 32 | hmac.doFinal(policy.getBytes("UTF-8"))) 33 | .replaceAll("\n", ""); 34 | res.setStatus(HttpServletResponse.SC_OK); 35 | res.setContentType("application/json"); 36 | res.getWriter().write("{\"signature\":\"" + signature + "\",\"policy\":\"" + policy + "\"}"); 37 | } catch (NoSuchAlgorithmException e) { 38 | throw new RuntimeException(e); 39 | } catch (InvalidKeyException e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/resources/META-INF/jdoconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | org.datanucleus.api.jpa.PersistenceProviderImpl 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # A default log4j configuration for log4j users. 2 | # 3 | # To use this configuration, deploy it into your application's WEB-INF/classes 4 | # directory. You are also encouraged to edit it as you like. 5 | 6 | # Configure the console as our one appender 7 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n 10 | 11 | # tighten logging on the DataNucleus Categories 12 | log4j.category.DataNucleus.JDO=WARN, A1 13 | log4j.category.DataNucleus.Persistence=WARN, A1 14 | log4j.category.DataNucleus.Cache=WARN, A1 15 | log4j.category.DataNucleus.MetaData=WARN, A1 16 | log4j.category.DataNucleus.General=WARN, A1 17 | log4j.category.DataNucleus.Utility=WARN, A1 18 | log4j.category.DataNucleus.Transaction=WARN, A1 19 | log4j.category.DataNucleus.Datastore=WARN, A1 20 | log4j.category.DataNucleus.ClassLoading=WARN, A1 21 | log4j.category.DataNucleus.Plugin=WARN, A1 22 | log4j.category.DataNucleus.ValueGeneration=WARN, A1 23 | log4j.category.DataNucleus.Enhancer=WARN, A1 24 | log4j.category.DataNucleus.SchemaTool=WARN, A1 25 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/WEB-INF/appengine-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | angular-file-upload 4 | 9-0-0 5 | 6 | 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/WEB-INF/logging.properties: -------------------------------------------------------------------------------- 1 | # A default java.util.logging configuration. 2 | # (All App Engine logging is through java.util.logging by default). 3 | # 4 | # To use this configuration, copy it into your application's WEB-INF 5 | # folder and add the following to your appengine-web.xml: 6 | # 7 | # 8 | # 9 | # 10 | # 11 | 12 | # Set the default logging level for all loggers to WARNING 13 | .level = WARNING 14 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | file_upload 7 | com.df.angularfileupload.FileUpload 8 | 9 | 10 | file_upload 11 | /upload 12 | 13 | 14 | s3_sign 15 | com.df.angularfileupload.S3Signature 16 | 17 | 18 | s3_sign 19 | /s3sign 20 | 21 | 22 | index.html 23 | 24 | 25 | SystemServiceServlet 26 | com.google.api.server.spi.SystemServiceServlet 27 | 28 | services 29 | 30 | 31 | 32 | 33 | SystemServiceServlet 34 | /_ah/spi/* 35 | 36 | 37 | cors_filter 38 | com.df.angularfileupload.CORSFilter 39 | 40 | 41 | cors_filter 42 | * 43 | 44 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/common.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica, arial, freesans, clean, sans-serif; 3 | } 4 | 5 | /* object { 6 | border: 3px solid red; 7 | } */ 8 | 9 | .upload-buttons input[type="file"] { 10 | width: 6.3em \0/ IE9; 11 | } 12 | 13 | .upload-button { 14 | Height: 26px; 15 | line-height: 30px; 16 | padding: 0 10px; 17 | background: #CCC; 18 | appearance: button; 19 | -moz-appearance: button; /* Firefox */ 20 | -webkit-appearance: button; /* Safari and Chrome */ 21 | position: relative; 22 | text-align: center; 23 | top: 7px; 24 | cursor: pointer; 25 | } 26 | 27 | .sel-file { 28 | padding: 1px 5px; 29 | font-size: smaller; 30 | color: grey; 31 | } 32 | 33 | .response { 34 | padding: 0; 35 | padding-top: 10px; 36 | margin: 3px 0; 37 | clear: both; 38 | list-style: none; 39 | } 40 | 41 | .response .sel-file li, .response .reqh { 42 | color: blue; 43 | padding-bottom: 5px; 44 | } 45 | 46 | fieldset { 47 | border: 1px solid #DDD; 48 | width: 620px; 49 | padding: 10px; 50 | line-height: 23px; 51 | } 52 | 53 | fieldset label { 54 | /*font-size: smaller;*/ 55 | } 56 | 57 | .progress { 58 | display: inline-block; 59 | width: 100px; 60 | border: 3px groove #CCC; 61 | } 62 | 63 | .progress div { 64 | font-size: smaller; 65 | background: orange; 66 | width: 0; 67 | } 68 | 69 | .drop-box { 70 | background: #F8F8F8; 71 | border: 5px dashed #DDD; 72 | width: 170px; 73 | height: 170px; 74 | text-align: center; 75 | padding: 100px 10px 0 10px; 76 | margin-left: 10px; 77 | } 78 | 79 | .up-buttons { 80 | float: right; 81 | } 82 | 83 | .drop-box.dragover { 84 | border: 5px dashed blue; 85 | } 86 | 87 | .drop-box.dragover-err { 88 | border: 5px dashed red; 89 | } 90 | 91 | /* for IE*/ 92 | .js-fileapi-wrapper { 93 | display: inline-block; 94 | vertical-align: middle; 95 | } 96 | 97 | button { 98 | padding: 1px 5px; 99 | font-size: smaller; 100 | margin: 0 3px; 101 | } 102 | 103 | .ng-v { 104 | float: right; 105 | } 106 | 107 | .thumb { 108 | float: left; 109 | width: 18px; 110 | height: 18px; 111 | padding-right: 10px; 112 | } 113 | 114 | form .thumb { 115 | width: 24px; 116 | height: 24px; 117 | float: none; 118 | position: relative; 119 | top: 7px; 120 | } 121 | 122 | form .progress { 123 | line-height: 15px; 124 | } 125 | 126 | .edit-area { 127 | font-size: 14px; 128 | background: black; 129 | color: #f9f9f9; 130 | padding: 5px 1px; 131 | } 132 | 133 | #htmlEdit { 134 | margin-bottom: 25px; 135 | } 136 | 137 | .edit-div { 138 | font-size: smaller; 139 | } 140 | 141 | .CodeMirror { 142 | font-size: 14px; 143 | border: 1px solid #ccc; 144 | margin-bottom: 15px; 145 | } 146 | 147 | form button { 148 | padding: 3px 10px; 149 | font-weight: bold; 150 | margin-top: 10px; 151 | } 152 | 153 | .sub { 154 | font-size: smaller; 155 | color: #777; 156 | padding-top: 5px; 157 | padding-left: 25px; 158 | } 159 | 160 | .err { 161 | font-size: 12px; 162 | color: #C53F00; 163 | margin: 15px; 164 | padding: 15px; 165 | background-color: #F0F0F0; 166 | border: 1px solid black; 167 | } 168 | 169 | .s3 { 170 | font-size: smaller; 171 | color: #333; 172 | margin-left: 20px; 173 | } 174 | 175 | .s3 fieldset { 176 | border: 1px solid #AAA; 177 | } 178 | 179 | .s3 label { 180 | width: 180px; 181 | display: inline-block; 182 | } 183 | 184 | .s3 input { 185 | width: 300px; 186 | } 187 | 188 | .s3 .helper { 189 | margin-left: 5px; 190 | } 191 | 192 | .howto { 193 | margin-left: 10px; 194 | line-height: 20px; 195 | } 196 | 197 | .server { 198 | margin-bottom: 20px; 199 | } 200 | 201 | .srv-title { 202 | font-weight: bold; 203 | padding: 5px 0 10px 0; 204 | } 205 | 206 | :not(output):-moz-ui-invalid { 207 | box-shadow: none; 208 | } 209 | 210 | .preview { 211 | clear: both; 212 | } 213 | 214 | .preview img, .preview audio, .preview video { 215 | max-width: 300px; 216 | max-height: 150px; 217 | float: right; 218 | } 219 | 220 | .custom { 221 | font-size: 14px; 222 | margin-left: 20px; 223 | } 224 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/donate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular file upload donate 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/img/tea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/img/tea.jpg -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/img/tea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/img/tea.png -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/js/FileAPI.flash.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/js/FileAPI.flash.swf -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/demo/src/main/webapp/js/ng-file-upload-shim.min.js: -------------------------------------------------------------------------------- 1 | /*! 9.1.2 */ 2 | !function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c){try{Object.defineProperty(a,b,{get:c})}catch(d){}}if(window.FileAPI||(window.FileAPI={}),!window.XMLHttpRequest)throw"AJAX is not supported. XMLHttpRequest is not defined.";if(FileAPI.shouldLoad=!window.FormData||FileAPI.forceLoad,FileAPI.shouldLoad){var c=function(a){if(!a.__listeners){a.upload||(a.upload={}),a.__listeners=[];var b=a.upload.addEventListener;a.upload.addEventListener=function(c,d){a.__listeners[c]=d,b&&b.apply(this,arguments)}}};a("open",function(a){return function(b,d,e){c(this),this.__url=d;try{a.apply(this,[b,d,e])}catch(f){f.message.indexOf("Access is denied")>-1&&(this.__origError=f,a.apply(this,[b,"_fix_for_ie_crossdomain__",e]))}}}),a("getResponseHeader",function(a){return function(b){return this.__fileApiXHR&&this.__fileApiXHR.getResponseHeader?this.__fileApiXHR.getResponseHeader(b):null==a?null:a.apply(this,[b])}}),a("getAllResponseHeaders",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.getAllResponseHeaders?this.__fileApiXHR.getAllResponseHeaders():null==a?null:a.apply(this)}}),a("abort",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.abort?this.__fileApiXHR.abort():null==a?null:a.apply(this)}}),a("setRequestHeader",function(a){return function(b,d){if("__setXHR_"===b){c(this);var e=d(this);e instanceof Function&&e(this)}else this.__requestHeaders=this.__requestHeaders||{},this.__requestHeaders[b]=d,a.apply(this,arguments)}}),a("send",function(a){return function(){var c=this;if(arguments[0]&&arguments[0].__isFileAPIShim){var d=arguments[0],e={url:c.__url,jsonp:!1,cache:!0,complete:function(a,d){a&&angular.isString(a)&&-1!==a.indexOf("#2174")&&(a=null),c.__completed=!0,!a&&c.__listeners.load&&c.__listeners.load({type:"load",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),!a&&c.__listeners.loadend&&c.__listeners.loadend({type:"loadend",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),"abort"===a&&c.__listeners.abort&&c.__listeners.abort({type:"abort",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),void 0!==d.status&&b(c,"status",function(){return 0===d.status&&a&&"abort"!==a?500:d.status}),void 0!==d.statusText&&b(c,"statusText",function(){return d.statusText}),b(c,"readyState",function(){return 4}),void 0!==d.response&&b(c,"response",function(){return d.response});var e=d.responseText||(a&&0===d.status&&"abort"!==a?a:void 0);b(c,"responseText",function(){return e}),b(c,"response",function(){return e}),a&&b(c,"err",function(){return a}),c.__fileApiXHR=d,c.onreadystatechange&&c.onreadystatechange(),c.onload&&c.onload()},progress:function(a){if(a.target=c,c.__listeners.progress&&c.__listeners.progress(a),c.__total=a.total,c.__loaded=a.loaded,a.total===a.loaded){var b=this;setTimeout(function(){c.__completed||(c.getAllResponseHeaders=function(){},b.complete(null,{status:204,statusText:"No Content"}))},FileAPI.noContentTimeout||1e4)}},headers:c.__requestHeaders};e.data={},e.files={};for(var f=0;f-1){e=h.substring(0,g+1);break}null==FileAPI.staticPath&&(FileAPI.staticPath=e),i.setAttribute("src",d||e+"FileAPI.js"),document.getElementsByTagName("head")[0].appendChild(i)}FileAPI.ngfFixIE=function(d,e,f){if(!b())throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';var g=function(){d.attr("disabled")?e&&e.removeClass("js-fileapi-wrapper"):(e.attr("__ngf_flash_")||(e.unbind("change"),e.unbind("click"),e.bind("change",function(a){h.apply(this,[a]),f.apply(this,[a])}),e.attr("__ngf_flash_","true")),e.addClass("js-fileapi-wrapper"),a(d)||e.css("position","absolute").css("top",c(d[0]).top+"px").css("left",c(d[0]).left+"px").css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("filter","alpha(opacity=0)").css("display",d.css("display")).css("overflow","hidden").css("z-index","900000").css("visibility","visible"))};d.bind("mouseenter",g);var h=function(a){for(var b=FileAPI.getFiles(a),c=0;c 2 | 3 | 4 | angular-file-upload 5 | Angular file upload 6 | 9.1.2 7 | Danial Farid, Georgios Diamantopoulos (nuget package) 8 | Danial Farid 9 | https://github.com/danialfarid/ng-file-upload/blob/master/LICENSE 10 | https://github.com/danialfarid/ng-file-upload 11 | false 12 | Light-weight HTML5 and cross-browser AngularJS directives for file upload, progress, abort, drag and drop 13 | angularjs upload 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/nuget/build.bat: -------------------------------------------------------------------------------- 1 | NuGet Update -self 2 | 3 | rmdir /s /q content 4 | mkdir content 5 | mkdir content\scripts 6 | copy ..\dist\* content\scripts 7 | del angular-file-upload.* 8 | 9 | NuGet Pack Package.nuspec 10 | 11 | for %%f in (angular-file-upload.*) do ( 12 | NuGet Push %%f 13 | rmdir /s /q content 14 | del %%f 15 | ) 16 | 17 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/nuget/nuget.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # add a simple 'nuget' command to Mac OS X under Mono 3 | # get NuGet.exe binary from http://nuget.codeplex.com/releases/view/58939 4 | # get Microsoft.Build.dll from a Windows .NET 4.0 installation 5 | # copy to /usr/local/bin and Robert is your father's brother.... 6 | # 7 | PATH=/usr/local/bin:$PATH 8 | mono --runtime=v4.0 /usr/local/bin/NuGet.exe $* 9 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-file-upload", 3 | "version": "9.1.2", 4 | "devDependencies": { 5 | "grunt": "^0.4.5", 6 | "grunt-contrib-concat": "^0.5.1", 7 | "grunt-contrib-clean": "^0.6.0", 8 | "grunt-contrib-jshint": "^0.11.0", 9 | "grunt-contrib-uglify": "^0.8.0", 10 | "grunt-contrib-watch": "^0.6.1", 11 | "grunt-contrib-copy": "~0.4.1", 12 | "grunt-text-replace": "~0.3.12", 13 | "grunt-git": "~0.2.14", 14 | "grunt-serve": "^0.1.6", 15 | "jshint-stylish": "^1.0.0", 16 | "load-grunt-tasks": "^3.1.0" 17 | }, 18 | "description": "An AngularJS directive for file upload using HTML5 with FileAPI polyfill for unsupported browsers", 19 | "main": "index.js", 20 | "scripts": { 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/danialfarid/ng-file-upload.git" 26 | }, 27 | "keywords": [ 28 | "angularjs", 29 | "ng-file-upload", 30 | "file-upload", 31 | "javascript" 32 | ], 33 | "author": { 34 | "name": "danial.farid@gmail.com" 35 | }, 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/danialfarid/ng-file-upload/issues" 39 | }, 40 | "homepage": "https://github.com/danialfarid/ng-file-upload", 41 | "gitHead": "0682f078acfcc29f7cc8d8b55a8d512d16673a87", 42 | "_id": "ng-file-upload@9.1.2", 43 | "_shasum": "d9b7ed6cd00a69b01a51cdb41e47c2e79102f453", 44 | "_from": "ng-file-upload@>=9.1.2 <10.0.0", 45 | "_npmVersion": "2.12.1", 46 | "_nodeVersion": "0.12.0", 47 | "_npmUser": { 48 | "name": "danial.farid", 49 | "email": "danial.farid@gmail.com" 50 | }, 51 | "maintainers": [ 52 | { 53 | "name": "danial.farid", 54 | "email": "danial.farid@gmail.com" 55 | } 56 | ], 57 | "dist": { 58 | "shasum": "d9b7ed6cd00a69b01a51cdb41e47c2e79102f453", 59 | "tarball": "http://registry.npmjs.org/ng-file-upload/-/ng-file-upload-9.1.2.tgz" 60 | }, 61 | "directories": {}, 62 | "_resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-9.1.2.tgz", 63 | "readme": "ERROR: No README data found!" 64 | } 65 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/release.sh: -------------------------------------------------------------------------------- 1 | echo version: $2 2 | echo message: $1 3 | 4 | grunt 5 | git add . 6 | git add -u . 7 | git commit -am "$1" 8 | git pull 9 | git push 10 | cd ../angular-file-upload-shim-bower 11 | git add . 12 | git add -u . 13 | git commit -am "$2" 14 | git pull 15 | git push 16 | cd ../angular-file-upload-bower 17 | git add . 18 | git add -u . 19 | git commit -am "$2" 20 | git pull 21 | git push 22 | 23 | 24 | API_JSON=$(printf '{"tag_name": "%s","target_commitish": "master","name": "Version %s","body": "%s","draft": false,"prerelease": false}' $2 $2 "$1") 25 | 26 | echo commit json: $API_JSON 27 | 28 | curl --data "$API_JSON" https://api.github.com/repos/danialfarid/ng-file-upload/releases?access_token=$3 29 | 30 | curl --data "$API_JSON" https://api.github.com/repos/danialfarid/ng-file-upload-shim-bower/releases?access_token=$3 31 | 32 | curl --data "$API_JSON" https://api.github.com/repos/danialfarid/ng-file-upload-bower/releases?access_token=$3 33 | 34 | cd ../ng-file-upload 35 | npm publish 36 | 37 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/FileAPI.flash.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onstottj/sample-menu-ordering-system/9431336871659069741a741dab27a4a906c7297c/src/main/webapp/js/node_modules/ng-file-upload/src/FileAPI.flash.swf -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/exif.js: -------------------------------------------------------------------------------- 1 | // customized version of https://github.com/exif-js/exif-js 2 | ngFileUpload.service('UploadExif', ['UploadResize', '$q', function (UploadResize, $q) { 3 | var upload = UploadResize; 4 | 5 | function findEXIFinJPEG(file) { 6 | var dataView = new DataView(file); 7 | 8 | if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) { 9 | return 'Not a valid JPEG'; 10 | } 11 | 12 | var offset = 2, 13 | length = file.byteLength, 14 | marker; 15 | 16 | while (offset < length) { 17 | if (dataView.getUint8(offset) !== 0xFF) { 18 | return 'Not a valid marker at offset ' + offset + ', found: ' + dataView.getUint8(offset); 19 | } 20 | 21 | marker = dataView.getUint8(offset + 1); 22 | if (marker === 225) { 23 | return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); 24 | } else { 25 | offset += 2 + dataView.getUint16(offset + 2); 26 | } 27 | } 28 | } 29 | 30 | function readOrientation(file, tiffStart, dirStart, bigEnd) { 31 | var entries = file.getUint16(dirStart, !bigEnd), 32 | entryOffset, i; 33 | 34 | for (i = 0; i < entries; i++) { 35 | entryOffset = dirStart + i * 12 + 2; 36 | var val = file.getUint16(entryOffset, !bigEnd); 37 | if (0x0112 === val) { 38 | return readTagValue(file, entryOffset, tiffStart, bigEnd); 39 | } 40 | } 41 | return null; 42 | } 43 | 44 | function readTagValue(file, entryOffset, tiffStart, bigEnd) { 45 | var numValues = file.getUint32(entryOffset + 4, !bigEnd), 46 | valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, offset, vals, n; 47 | 48 | if (numValues === 1) { 49 | return file.getUint16(entryOffset + 8, !bigEnd); 50 | } else { 51 | offset = numValues > 2 ? valueOffset : (entryOffset + 8); 52 | vals = []; 53 | for (n = 0; n < numValues; n++) { 54 | vals[n] = file.getUint16(offset + 2 * n, !bigEnd); 55 | } 56 | return vals; 57 | } 58 | } 59 | 60 | function getStringFromDB(buffer, start, length) { 61 | var outstr = ''; 62 | for (var n = start; n < start + length; n++) { 63 | outstr += String.fromCharCode(buffer.getUint8(n)); 64 | } 65 | return outstr; 66 | } 67 | 68 | function readEXIFData(file, start) { 69 | if (getStringFromDB(file, start, 4) !== 'Exif') { 70 | return 'Not valid EXIF data! ' + getStringFromDB(file, start, 4); 71 | } 72 | 73 | var bigEnd, 74 | tiffOffset = start + 6; 75 | 76 | // test for TIFF validity and endianness 77 | if (file.getUint16(tiffOffset) === 0x4949) { 78 | bigEnd = false; 79 | } else if (file.getUint16(tiffOffset) === 0x4D4D) { 80 | bigEnd = true; 81 | } else { 82 | return 'Not valid TIFF data! (no 0x4949 or 0x4D4D)'; 83 | } 84 | 85 | if (file.getUint16(tiffOffset + 2, !bigEnd) !== 0x002A) { 86 | return 'Not valid TIFF data! (no 0x002A)'; 87 | } 88 | 89 | var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd); 90 | 91 | if (firstIFDOffset < 0x00000008) { 92 | return 'Not valid TIFF data! (First offset less than 8)', file.getUint32(tiffOffset + 4, !bigEnd); 93 | } 94 | 95 | return readOrientation(file, tiffOffset, tiffOffset + firstIFDOffset, bigEnd); 96 | 97 | } 98 | 99 | upload.isExifSupported = function() { 100 | return window.FileReader && new FileReader().readAsArrayBuffer && upload.isResizeSupported(); 101 | }; 102 | 103 | upload.orientation = function (file) { 104 | if (file.$ngfOrientation != null) { 105 | return upload.emptyPromise(file.$ngfOrientation); 106 | } 107 | var defer = $q.defer(); 108 | var fileReader = new FileReader(); 109 | fileReader.onload = function (e) { 110 | var orientation = findEXIFinJPEG(e.target.result); 111 | if (angular.isString(orientation)) { 112 | defer.reject(orientation); 113 | } else { 114 | file.$ngfOrientation = orientation; 115 | defer.resolve(orientation); 116 | } 117 | }; 118 | fileReader.onerror = function (e) { 119 | defer.reject(e); 120 | }; 121 | 122 | fileReader.readAsArrayBuffer(file); 123 | return defer.promise; 124 | }; 125 | 126 | 127 | function applyTransform(ctx, orientation, width, height) { 128 | switch (orientation) { 129 | case 2: 130 | return ctx.transform(-1, 0, 0, 1, width, 0); 131 | case 3: 132 | return ctx.transform(-1, 0, 0, -1, width, height); 133 | case 4: 134 | return ctx.transform(1, 0, 0, -1, 0, height); 135 | case 5: 136 | return ctx.transform(0, 1, 1, 0, 0, 0); 137 | case 6: 138 | return ctx.transform(0, 1, -1, 0, height, 0); 139 | case 7: 140 | return ctx.transform(0, -1, -1, 0, height, width); 141 | case 8: 142 | return ctx.transform(0, -1, 1, 0, 0, width); 143 | } 144 | } 145 | 146 | upload.applyExifRotation = function (file) { 147 | if (file.type.indexOf('image/jpeg') !== 0) { 148 | return upload.emptyPromise(file); 149 | } 150 | 151 | var deferred = $q.defer(); 152 | upload.orientation(file).then(function (orientation) { 153 | if (!orientation || orientation < 2 || orientation > 8) { 154 | deferred.resolve(file); 155 | } 156 | upload.dataUrl(file, true).then(function (url) { 157 | var canvas = document.createElement('canvas'); 158 | var img = document.createElement('img'); 159 | 160 | img.onload = function () { 161 | try { 162 | canvas.width = orientation > 4 ? img.height : img.width; 163 | canvas.height = orientation > 4 ? img.width : img.height; 164 | var ctx = canvas.getContext('2d'); 165 | applyTransform(ctx, orientation, img.width, img.height); 166 | ctx.drawImage(img, 0, 0); 167 | var dataUrl = canvas.toDataURL(file.type || 'image/WebP', 1.0); 168 | var blob = upload.dataUrltoBlob(dataUrl, file.name); 169 | deferred.resolve(blob); 170 | } catch (e) { 171 | deferred.reject(e); 172 | } 173 | }; 174 | img.onerror = function () { 175 | deferred.reject(); 176 | }; 177 | img.src = url; 178 | }, function (e) { 179 | deferred.reject(e); 180 | }); 181 | }, function (e) { 182 | deferred.reject(e); 183 | }); 184 | return deferred.promise; 185 | }; 186 | 187 | return upload; 188 | }]); 189 | 190 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/model.js: -------------------------------------------------------------------------------- 1 | ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) { 2 | var upload = UploadExif; 3 | upload.getAttrWithDefaults = function (attr, name) { 4 | if (attr[name] != null) return attr[name]; 5 | var def = upload.defaults[name]; 6 | return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def))); 7 | }; 8 | 9 | upload.attrGetter = function (name, attr, scope, params) { 10 | var attrVal = this.getAttrWithDefaults(attr, name); 11 | if (scope) { 12 | try { 13 | if (params) { 14 | return $parse(attrVal)(scope, params); 15 | } else { 16 | return $parse(attrVal)(scope); 17 | } 18 | } catch (e) { 19 | // hangle string value without single qoute 20 | if (name.search(/min|max|pattern/i)) { 21 | return attrVal; 22 | } else { 23 | throw e; 24 | } 25 | } 26 | } else { 27 | return attrVal; 28 | } 29 | }; 30 | 31 | upload.shouldUpdateOn = function (type, attr, scope) { 32 | var modelOptions = upload.attrGetter('ngModelOptions', attr, scope); 33 | if (modelOptions && modelOptions.updateOn) { 34 | return modelOptions.updateOn.split(' ').indexOf(type) > -1; 35 | } 36 | return true; 37 | }; 38 | 39 | upload.emptyPromise = function () { 40 | var d = $q.defer(); 41 | var args = arguments; 42 | $timeout(function () { 43 | d.resolve.apply(d, args); 44 | }); 45 | return d.promise; 46 | }; 47 | 48 | upload.happyPromise = function (promise, data) { 49 | var d = $q.defer(); 50 | promise.then(function (result) { 51 | d.resolve(result); 52 | }, function (error) { 53 | $timeout(function () { 54 | throw error; 55 | }); 56 | d.resolve(data); 57 | }); 58 | return d.promise; 59 | }; 60 | 61 | function applyExifRotations(files) { 62 | var promises = [upload.emptyPromise()]; 63 | angular.forEach(files, function (f, i) { 64 | if (f.type.indexOf('image/jpeg') === 0) { 65 | promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) { 66 | files.splice(i, 1, fixedFile); 67 | })); 68 | } 69 | }); 70 | return $q.all(promises); 71 | } 72 | 73 | function resize(files, attr, scope) { 74 | var param = upload.attrGetter('ngfResize', attr, scope); 75 | if (!param || !upload.isResizeSupported() || !files.length) return upload.emptyPromise(); 76 | if (!param.width || !param.height) throw 'width and height are mandatory for ngf-resize'; 77 | var promises = [upload.emptyPromise()]; 78 | angular.forEach(files, function (f, i) { 79 | if (f.type.indexOf('image') === 0) { 80 | var promise = upload.resize(f, param.width, param.height, param.quality); 81 | promises.push(promise); 82 | promise.then(function (resizedFile) { 83 | files.splice(i, 1, resizedFile); 84 | }, function (e) { 85 | f.$error = 'resize'; 86 | f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name); 87 | }); 88 | } 89 | }); 90 | return $q.all(promises); 91 | } 92 | 93 | function handleKeep(files, prevFiles, attr, scope) { 94 | var dupFiles = []; 95 | var keep = upload.attrGetter('ngfKeep', attr, scope); 96 | if (keep) { 97 | var hasNew = false; 98 | 99 | if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) { 100 | var len = prevFiles.length; 101 | if (files) { 102 | for (var i = 0; i < files.length; i++) { 103 | for (var j = 0; j < len; j++) { 104 | if (files[i].name === prevFiles[j].name) { 105 | dupFiles.push(files[i]); 106 | break; 107 | } 108 | } 109 | if (j === len) { 110 | prevFiles.push(files[i]); 111 | hasNew = true; 112 | } 113 | } 114 | } 115 | files = prevFiles; 116 | } else { 117 | files = prevFiles.concat(files || []); 118 | } 119 | } 120 | return {files: files, dupFiles: dupFiles, keep: keep}; 121 | } 122 | 123 | upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) { 124 | function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) { 125 | var file = files && files.length ? files[0] : null; 126 | 127 | if (ngModel) { 128 | upload.applyModelValidation(ngModel, files); 129 | ngModel.$ngfModelChange = true; 130 | ngModel.$setViewValue(isSingleModel ? file : files); 131 | } 132 | 133 | if (fileChange) { 134 | $parse(fileChange)(scope, { 135 | $files: files, 136 | $file: file, 137 | $newFiles: newFiles, 138 | $duplicateFiles: dupFiles, 139 | $invalidFiles: invalidFiles, 140 | $event: evt 141 | }); 142 | } 143 | 144 | var invalidModel = upload.attrGetter('ngfModelInvalid', attr); 145 | if (invalidModel) { 146 | $timeout(function () { 147 | $parse(invalidModel).assign(scope, invalidFiles); 148 | }); 149 | } 150 | $timeout(function () { 151 | // scope apply changes 152 | }); 153 | } 154 | 155 | var newFiles = files; 156 | var prevFiles = ((ngModel && ngModel.$modelValue) || attr.$$ngfPrevFiles || []).slice(0); 157 | var keepResult = handleKeep(files, prevFiles, attr, scope); 158 | files = keepResult.files; 159 | var dupFiles = keepResult.dupFiles; 160 | var isSingleModel = !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr) && !keepResult.keep; 161 | 162 | attr.$$ngfPrevFiles = files; 163 | 164 | if (keepResult.keep && (!newFiles || !newFiles.length)) return; 165 | 166 | upload.validate(newFiles, ngModel, attr, scope).then(function () { 167 | if (noDelay) { 168 | update(files, [], newFiles, dupFiles, isSingleModel); 169 | } else { 170 | var options = upload.attrGetter('ngModelOptions', attr, scope); 171 | if (!options || !options.allowInvalid) { 172 | var valids = [], invalids = []; 173 | angular.forEach(files, function (file) { 174 | if (file.$error) { 175 | invalids.push(file); 176 | } else { 177 | valids.push(file); 178 | } 179 | }); 180 | files = valids; 181 | } 182 | var fixOrientation = upload.emptyPromise(files); 183 | if (upload.attrGetter('ngfFixOrientation', attr, scope) !== false && upload.isExifSupported()) { 184 | fixOrientation = applyExifRotations(files); 185 | } 186 | fixOrientation.then(function () { 187 | resize(files, attr, scope).then(function () { 188 | $timeout(function () { 189 | update(files, invalids, newFiles, dupFiles, isSingleModel); 190 | }, options && options.debounce ? options.debounce.change || options.debounce : 0); 191 | }, function (e) { 192 | throw 'Could not resize files ' + e; 193 | }); 194 | }); 195 | } 196 | }); 197 | 198 | // cleaning object url memories 199 | var l = prevFiles.length; 200 | while (l--) { 201 | var prevFile = prevFiles[l]; 202 | if (window.URL && prevFile.blobUrl) { 203 | URL.revokeObjectURL(prevFile.blobUrl); 204 | delete prevFile.blobUrl; 205 | } 206 | } 207 | }; 208 | 209 | return upload; 210 | }]); 211 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/resize.js: -------------------------------------------------------------------------------- 1 | ngFileUpload.service('UploadResize', ['UploadValidate', '$q', function (UploadValidate, $q) { 2 | var upload = UploadValidate; 3 | 4 | /** 5 | * Conserve aspect ratio of the original region. Useful when shrinking/enlarging 6 | * images to fit into a certain area. 7 | * Source: http://stackoverflow.com/a/14731922 8 | * 9 | * @param {Number} srcWidth Source area width 10 | * @param {Number} srcHeight Source area height 11 | * @param {Number} maxWidth Nestable area maximum available width 12 | * @param {Number} maxHeight Nestable area maximum available height 13 | * @return {Object} { width, height } 14 | */ 15 | var calculateAspectRatioFit = function (srcWidth, srcHeight, maxWidth, maxHeight) { 16 | var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight); 17 | return {width: srcWidth * ratio, height: srcHeight * ratio}; 18 | }; 19 | 20 | // Extracted from https://github.com/romelgomez/angular-firebase-image-upload/blob/master/app/scripts/fileUpload.js#L89 21 | var resize = function (imagen, width, height, quality, type) { 22 | var deferred = $q.defer(); 23 | var canvasElement = document.createElement('canvas'); 24 | var imageElement = document.createElement('img'); 25 | 26 | imageElement.onload = function () { 27 | try { 28 | if (!width) { 29 | width = imageElement.width; 30 | height = imageElement.height; 31 | } 32 | var dimensions = calculateAspectRatioFit(imageElement.width, imageElement.height, width, height); 33 | canvasElement.width = dimensions.width; 34 | canvasElement.height = dimensions.height; 35 | var context = canvasElement.getContext('2d'); 36 | context.drawImage(imageElement, 0, 0, dimensions.width, dimensions.height); 37 | deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 1.0)); 38 | } catch (e) { 39 | deferred.reject(e); 40 | } 41 | }; 42 | imageElement.onerror = function () { 43 | deferred.reject(); 44 | }; 45 | imageElement.src = imagen; 46 | return deferred.promise; 47 | }; 48 | 49 | upload.dataUrltoBlob = function (dataurl, name) { 50 | var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], 51 | bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); 52 | while (n--) { 53 | u8arr[n] = bstr.charCodeAt(n); 54 | } 55 | var blob = new Blob([u8arr], {type: mime}); 56 | blob.name = name; 57 | return blob; 58 | }; 59 | 60 | upload.isResizeSupported = function () { 61 | var elem = document.createElement('canvas'); 62 | return window.atob && elem.getContext && elem.getContext('2d'); 63 | }; 64 | 65 | if (upload.isResizeSupported()) { 66 | // add name getter to the blob constructor prototype 67 | Object.defineProperty(Blob.prototype, 'name', { 68 | get: function () { 69 | return this.$ngfName; 70 | }, 71 | set: function (v) { 72 | this.$ngfName = v; 73 | }, 74 | configurable: true 75 | }); 76 | } 77 | 78 | upload.resize = function (file, width, height, quality) { 79 | if (file.type.indexOf('image') !== 0) return upload.emptyPromise(file); 80 | 81 | var deferred = $q.defer(); 82 | upload.dataUrl(file, true).then(function (url) { 83 | resize(url, width, height, quality, file.type).then(function (dataUrl) { 84 | deferred.resolve(upload.dataUrltoBlob(dataUrl, file.name)); 85 | }, function () { 86 | deferred.reject(); 87 | }); 88 | }, function () { 89 | deferred.reject(); 90 | }); 91 | return deferred.promise; 92 | }; 93 | 94 | return upload; 95 | }]); 96 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/select.js: -------------------------------------------------------------------------------- 1 | ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { 2 | var generatedElems = []; 3 | 4 | function isDelayedClickSupported(ua) { 5 | // fix for android native browser < 4.4 and safari windows 6 | var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); 7 | if (m && m.length > 2) { 8 | var v = Upload.defaults.androidFixMinorVersion || 4; 9 | return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); 10 | } 11 | 12 | // safari on windows 13 | return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); 14 | } 15 | 16 | function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { 17 | /** @namespace attr.ngfSelect */ 18 | /** @namespace attr.ngfChange */ 19 | /** @namespace attr.ngModel */ 20 | /** @namespace attr.ngModelOptions */ 21 | /** @namespace attr.ngfMultiple */ 22 | /** @namespace attr.ngfCapture */ 23 | /** @namespace attr.ngfValidate */ 24 | /** @namespace attr.ngfKeep */ 25 | var attrGetter = function (name, scope) { 26 | return upload.attrGetter(name, attr, scope); 27 | }; 28 | 29 | function isInputTypeFile() { 30 | return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; 31 | } 32 | 33 | function fileChangeAttr() { 34 | return attrGetter('ngfChange') || attrGetter('ngfSelect'); 35 | } 36 | 37 | function changeFn(evt) { 38 | if (upload.shouldUpdateOn('change', attr, scope)) { 39 | var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; 40 | for (var i = 0; i < fileList.length; i++) { 41 | files.push(fileList[i]); 42 | } 43 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), 44 | files.length ? files : null, evt); 45 | } 46 | } 47 | 48 | upload.registerModelChangeValidator(ngModel, attr, scope); 49 | 50 | var unwatches = []; 51 | unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { 52 | fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); 53 | })); 54 | unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { 55 | fileElem.attr('capture', attrGetter('ngfCapture', scope)); 56 | })); 57 | attr.$observe('accept', function () { 58 | fileElem.attr('accept', attrGetter('accept')); 59 | }); 60 | unwatches.push(function () { 61 | if (attr.$$observers) delete attr.$$observers.accept; 62 | }); 63 | function bindAttrToFileInput(fileElem) { 64 | if (elem !== fileElem) { 65 | for (var i = 0; i < elem[0].attributes.length; i++) { 66 | var attribute = elem[0].attributes[i]; 67 | if (attribute.name !== 'type' && attribute.name !== 'class' && 68 | attribute.name !== 'id' && attribute.name !== 'style') { 69 | if (attribute.value == null || attribute.value === '') { 70 | if (attribute.name === 'required') attribute.value = 'required'; 71 | if (attribute.name === 'multiple') attribute.value = 'multiple'; 72 | } 73 | fileElem.attr(attribute.name, attribute.value); 74 | } 75 | } 76 | } 77 | } 78 | 79 | function createFileInput() { 80 | if (isInputTypeFile()) { 81 | return elem; 82 | } 83 | 84 | var fileElem = angular.element(''); 85 | bindAttrToFileInput(fileElem); 86 | 87 | fileElem.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') 88 | .css('width', '0px').css('height', '0px').css('border', 'none') 89 | .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); 90 | generatedElems.push({el: elem, ref: fileElem}); 91 | document.body.appendChild(fileElem[0]); 92 | 93 | return fileElem; 94 | } 95 | 96 | var initialTouchStartY = 0; 97 | 98 | function clickHandler(evt) { 99 | if (elem.attr('disabled') || attrGetter('ngfSelectDisabled', scope)) return false; 100 | 101 | var r = handleTouch(evt); 102 | if (r != null) return r; 103 | 104 | resetModel(evt); 105 | 106 | // fix for md when the element is removed from the DOM and added back #460 107 | try { 108 | if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { 109 | generatedElems.push({el: elem, ref: fileElem}); 110 | document.body.appendChild(fileElem[0]); 111 | fileElem.bind('change', changeFn); 112 | } 113 | } catch(e){/*ignore*/} 114 | 115 | if (isDelayedClickSupported(navigator.userAgent)) { 116 | setTimeout(function () { 117 | fileElem[0].click(); 118 | }, 0); 119 | } else { 120 | fileElem[0].click(); 121 | } 122 | 123 | return false; 124 | } 125 | 126 | function handleTouch(evt) { 127 | var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); 128 | if (evt.type === 'touchstart') { 129 | initialTouchStartY = touches ? touches[0].clientY : 0; 130 | return true; // don't block event default 131 | } else { 132 | evt.stopPropagation(); 133 | evt.preventDefault(); 134 | 135 | // prevent scroll from triggering event 136 | if (evt.type === 'touchend') { 137 | var currentLocation = touches ? touches[0].clientY : 0; 138 | if (Math.abs(currentLocation - initialTouchStartY) > 20) return false; 139 | } 140 | } 141 | } 142 | 143 | var fileElem = elem; 144 | 145 | function resetModel(evt) { 146 | if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { 147 | fileElem.val(null); 148 | upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); 149 | } 150 | } 151 | 152 | if (!isInputTypeFile()) { 153 | fileElem = createFileInput(); 154 | } 155 | fileElem.bind('change', changeFn); 156 | 157 | if (!isInputTypeFile()) { 158 | elem.bind('click touchstart touchend', clickHandler); 159 | } else { 160 | elem.bind('click', resetModel); 161 | } 162 | 163 | function ie10SameFileSelectFix(evt) { 164 | if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { 165 | if (!fileElem[0].parentNode) { 166 | fileElem = null; 167 | return; 168 | } 169 | evt.preventDefault(); 170 | evt.stopPropagation(); 171 | fileElem.unbind('click'); 172 | var clone = fileElem.clone(); 173 | fileElem.replaceWith(clone); 174 | fileElem = clone; 175 | fileElem.attr('__ngf_ie10_Fix_', 'true'); 176 | fileElem.bind('change', changeFn); 177 | fileElem.bind('click', ie10SameFileSelectFix); 178 | fileElem[0].click(); 179 | return false; 180 | } else { 181 | fileElem.removeAttr('__ngf_ie10_Fix_'); 182 | } 183 | } 184 | 185 | if (navigator.appVersion.indexOf('MSIE 10') !== -1) { 186 | fileElem.bind('click', ie10SameFileSelectFix); 187 | } 188 | 189 | if (ngModel) ngModel.$formatters.push(function (val) { 190 | if (val == null || val.length === 0) { 191 | if (fileElem.val()) { 192 | fileElem.val(null); 193 | } 194 | } 195 | return val; 196 | }); 197 | 198 | scope.$on('$destroy', function () { 199 | if (!isInputTypeFile()) fileElem.remove(); 200 | angular.forEach(unwatches, function (unwatch) { 201 | unwatch(); 202 | }); 203 | }); 204 | 205 | $timeout(function () { 206 | for (var i = 0; i < generatedElems.length; i++) { 207 | var g = generatedElems[i]; 208 | if (!document.body.contains(g.el[0])) { 209 | generatedElems.splice(i, 1); 210 | g.ref.remove(); 211 | } 212 | } 213 | }); 214 | 215 | if (window.FileAPI && window.FileAPI.ngfFixIE) { 216 | window.FileAPI.ngfFixIE(elem, fileElem, changeFn); 217 | } 218 | } 219 | 220 | return { 221 | restrict: 'AEC', 222 | require: '?ngModel', 223 | link: function (scope, elem, attr, ngModel) { 224 | linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); 225 | } 226 | }; 227 | }]); 228 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/shim-elem.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | /** @namespace FileAPI.forceLoad */ 3 | /** @namespace window.FileAPI.jsUrl */ 4 | /** @namespace window.FileAPI.jsPath */ 5 | 6 | function isInputTypeFile(elem) { 7 | return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file'; 8 | } 9 | 10 | function hasFlash() { 11 | try { 12 | var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); 13 | if (fo) return true; 14 | } catch (e) { 15 | if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true; 16 | } 17 | return false; 18 | } 19 | 20 | function getOffset(obj) { 21 | var left = 0, top = 0; 22 | 23 | if (window.jQuery) { 24 | return jQuery(obj).offset(); 25 | } 26 | 27 | if (obj.offsetParent) { 28 | do { 29 | left += (obj.offsetLeft - obj.scrollLeft); 30 | top += (obj.offsetTop - obj.scrollTop); 31 | obj = obj.offsetParent; 32 | } while (obj); 33 | } 34 | return { 35 | left: left, 36 | top: top 37 | }; 38 | } 39 | 40 | if (FileAPI.shouldLoad) { 41 | FileAPI.hasFlash = hasFlash(); 42 | 43 | //load FileAPI 44 | if (FileAPI.forceLoad) { 45 | FileAPI.html5 = false; 46 | } 47 | 48 | if (!FileAPI.upload) { 49 | var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src; 50 | if (window.FileAPI.jsUrl) { 51 | jsUrl = window.FileAPI.jsUrl; 52 | } else if (window.FileAPI.jsPath) { 53 | basePath = window.FileAPI.jsPath; 54 | } else { 55 | for (i = 0; i < allScripts.length; i++) { 56 | src = allScripts[i].src; 57 | index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/); 58 | if (index > -1) { 59 | basePath = src.substring(0, index + 1); 60 | break; 61 | } 62 | } 63 | } 64 | 65 | if (FileAPI.staticPath == null) FileAPI.staticPath = basePath; 66 | script.setAttribute('src', jsUrl || basePath + 'FileAPI.js'); 67 | document.getElementsByTagName('head')[0].appendChild(script); 68 | } 69 | 70 | FileAPI.ngfFixIE = function (elem, fileElem, changeFn) { 71 | if (!hasFlash()) { 72 | throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; 73 | } 74 | var fixInputStyle = function () { 75 | if (elem.attr('disabled')) { 76 | if (fileElem) fileElem.removeClass('js-fileapi-wrapper'); 77 | } else { 78 | if (!fileElem.attr('__ngf_flash_')) { 79 | fileElem.unbind('change'); 80 | fileElem.unbind('click'); 81 | fileElem.bind('change', function (evt) { 82 | fileApiChangeFn.apply(this, [evt]); 83 | changeFn.apply(this, [evt]); 84 | }); 85 | fileElem.attr('__ngf_flash_', 'true'); 86 | } 87 | fileElem.addClass('js-fileapi-wrapper'); 88 | if (!isInputTypeFile(elem)) { 89 | fileElem.css('position', 'absolute') 90 | .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px') 91 | .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') 92 | .css('filter', 'alpha(opacity=0)').css('display', elem.css('display')) 93 | .css('overflow', 'hidden').css('z-index', '900000') 94 | .css('visibility', 'visible'); 95 | } 96 | } 97 | }; 98 | 99 | elem.bind('mouseenter', fixInputStyle); 100 | 101 | var fileApiChangeFn = function (evt) { 102 | var files = FileAPI.getFiles(evt); 103 | //just a double check for #233 104 | for (var i = 0; i < files.length; i++) { 105 | if (files[i].size === undefined) files[i].size = 0; 106 | if (files[i].name === undefined) files[i].name = 'file'; 107 | if (files[i].type === undefined) files[i].type = 'undefined'; 108 | } 109 | if (!evt.target) { 110 | evt.target = {}; 111 | } 112 | evt.target.files = files; 113 | // if evt.target.files is not writable use helper field 114 | if (evt.target.files !== files) { 115 | evt.__files_ = files; 116 | } 117 | (evt.__files_ || evt.target.files).item = function (i) { 118 | return (evt.__files_ || evt.target.files)[i] || null; 119 | }; 120 | }; 121 | }; 122 | 123 | FileAPI.disableFileInput = function (elem, disable) { 124 | if (disable) { 125 | elem.removeClass('js-fileapi-wrapper'); 126 | } else { 127 | elem.addClass('js-fileapi-wrapper'); 128 | } 129 | }; 130 | } 131 | })(); 132 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/shim-filereader.js: -------------------------------------------------------------------------------- 1 | if (!window.FileReader) { 2 | window.FileReader = function () { 3 | var _this = this, loadStarted = false; 4 | this.listeners = {}; 5 | this.addEventListener = function (type, fn) { 6 | _this.listeners[type] = _this.listeners[type] || []; 7 | _this.listeners[type].push(fn); 8 | }; 9 | this.removeEventListener = function (type, fn) { 10 | if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1); 11 | }; 12 | this.dispatchEvent = function (evt) { 13 | var list = _this.listeners[evt.type]; 14 | if (list) { 15 | for (var i = 0; i < list.length; i++) { 16 | list[i].call(_this, evt); 17 | } 18 | } 19 | }; 20 | this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null; 21 | 22 | var constructEvent = function (type, evt) { 23 | var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error}; 24 | if (evt.result != null) e.target.result = evt.result; 25 | return e; 26 | }; 27 | var listener = function (evt) { 28 | if (!loadStarted) { 29 | loadStarted = true; 30 | if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt)); 31 | } 32 | var e; 33 | if (evt.type === 'load') { 34 | if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt)); 35 | e = constructEvent('load', evt); 36 | if (_this.onload) _this.onload(e); 37 | _this.dispatchEvent(e); 38 | } else if (evt.type === 'progress') { 39 | e = constructEvent('progress', evt); 40 | if (_this.onprogress) _this.onprogress(e); 41 | _this.dispatchEvent(e); 42 | } else { 43 | e = constructEvent('error', evt); 44 | if (_this.onerror) _this.onerror(e); 45 | _this.dispatchEvent(e); 46 | } 47 | }; 48 | this.readAsDataURL = function (file) { 49 | FileAPI.readAsDataURL(file, listener); 50 | }; 51 | this.readAsText = function (file) { 52 | FileAPI.readAsText(file, listener); 53 | }; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/src/shim-upload.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, 3 | * progress, resize, thumbnail, preview, validation and CORS 4 | * FileAPI Flash shim for old browsers not supporting FormData 5 | * @author Danial 6 | * @version <%= pkg.version %> 7 | */ 8 | 9 | (function () { 10 | /** @namespace FileAPI.noContentTimeout */ 11 | 12 | function patchXHR(fnName, newFn) { 13 | window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]); 14 | } 15 | 16 | function redefineProp(xhr, prop, fn) { 17 | try { 18 | Object.defineProperty(xhr, prop, {get: fn}); 19 | } catch (e) {/*ignore*/ 20 | } 21 | } 22 | 23 | if (!window.FileAPI) { 24 | window.FileAPI = {}; 25 | } 26 | 27 | if (!window.XMLHttpRequest) { 28 | throw 'AJAX is not supported. XMLHttpRequest is not defined.'; 29 | } 30 | 31 | FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad; 32 | if (FileAPI.shouldLoad) { 33 | var initializeUploadListener = function (xhr) { 34 | if (!xhr.__listeners) { 35 | if (!xhr.upload) xhr.upload = {}; 36 | xhr.__listeners = []; 37 | var origAddEventListener = xhr.upload.addEventListener; 38 | xhr.upload.addEventListener = function (t, fn) { 39 | xhr.__listeners[t] = fn; 40 | if (origAddEventListener) origAddEventListener.apply(this, arguments); 41 | }; 42 | } 43 | }; 44 | 45 | patchXHR('open', function (orig) { 46 | return function (m, url, b) { 47 | initializeUploadListener(this); 48 | this.__url = url; 49 | try { 50 | orig.apply(this, [m, url, b]); 51 | } catch (e) { 52 | if (e.message.indexOf('Access is denied') > -1) { 53 | this.__origError = e; 54 | orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]); 55 | } 56 | } 57 | }; 58 | }); 59 | 60 | patchXHR('getResponseHeader', function (orig) { 61 | return function (h) { 62 | return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h])); 63 | }; 64 | }); 65 | 66 | patchXHR('getAllResponseHeaders', function (orig) { 67 | return function () { 68 | return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this)); 69 | }; 70 | }); 71 | 72 | patchXHR('abort', function (orig) { 73 | return function () { 74 | return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this)); 75 | }; 76 | }); 77 | 78 | patchXHR('setRequestHeader', function (orig) { 79 | return function (header, value) { 80 | if (header === '__setXHR_') { 81 | initializeUploadListener(this); 82 | var val = value(this); 83 | // fix for angular < 1.2.0 84 | if (val instanceof Function) { 85 | val(this); 86 | } 87 | } else { 88 | this.__requestHeaders = this.__requestHeaders || {}; 89 | this.__requestHeaders[header] = value; 90 | orig.apply(this, arguments); 91 | } 92 | }; 93 | }); 94 | 95 | patchXHR('send', function (orig) { 96 | return function () { 97 | var xhr = this; 98 | if (arguments[0] && arguments[0].__isFileAPIShim) { 99 | var formData = arguments[0]; 100 | var config = { 101 | url: xhr.__url, 102 | jsonp: false, //removes the callback form param 103 | cache: true, //removes the ?fileapiXXX in the url 104 | complete: function (err, fileApiXHR) { 105 | if (err && angular.isString(err) && err.indexOf('#2174') !== -1) { 106 | // this error seems to be fine the file is being uploaded properly. 107 | err = null; 108 | } 109 | xhr.__completed = true; 110 | if (!err && xhr.__listeners.load) 111 | xhr.__listeners.load({ 112 | type: 'load', 113 | loaded: xhr.__loaded, 114 | total: xhr.__total, 115 | target: xhr, 116 | lengthComputable: true 117 | }); 118 | if (!err && xhr.__listeners.loadend) 119 | xhr.__listeners.loadend({ 120 | type: 'loadend', 121 | loaded: xhr.__loaded, 122 | total: xhr.__total, 123 | target: xhr, 124 | lengthComputable: true 125 | }); 126 | if (err === 'abort' && xhr.__listeners.abort) 127 | xhr.__listeners.abort({ 128 | type: 'abort', 129 | loaded: xhr.__loaded, 130 | total: xhr.__total, 131 | target: xhr, 132 | lengthComputable: true 133 | }); 134 | if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () { 135 | return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status; 136 | }); 137 | if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () { 138 | return fileApiXHR.statusText; 139 | }); 140 | redefineProp(xhr, 'readyState', function () { 141 | return 4; 142 | }); 143 | if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () { 144 | return fileApiXHR.response; 145 | }); 146 | var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined); 147 | redefineProp(xhr, 'responseText', function () { 148 | return resp; 149 | }); 150 | redefineProp(xhr, 'response', function () { 151 | return resp; 152 | }); 153 | if (err) redefineProp(xhr, 'err', function () { 154 | return err; 155 | }); 156 | xhr.__fileApiXHR = fileApiXHR; 157 | if (xhr.onreadystatechange) xhr.onreadystatechange(); 158 | if (xhr.onload) xhr.onload(); 159 | }, 160 | progress: function (e) { 161 | e.target = xhr; 162 | if (xhr.__listeners.progress) xhr.__listeners.progress(e); 163 | xhr.__total = e.total; 164 | xhr.__loaded = e.loaded; 165 | if (e.total === e.loaded) { 166 | // fix flash issue that doesn't call complete if there is no response text from the server 167 | var _this = this; 168 | setTimeout(function () { 169 | if (!xhr.__completed) { 170 | xhr.getAllResponseHeaders = function () { 171 | }; 172 | _this.complete(null, {status: 204, statusText: 'No Content'}); 173 | } 174 | }, FileAPI.noContentTimeout || 10000); 175 | } 176 | }, 177 | headers: xhr.__requestHeaders 178 | }; 179 | config.data = {}; 180 | config.files = {}; 181 | for (var i = 0; i < formData.data.length; i++) { 182 | var item = formData.data[i]; 183 | if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) { 184 | config.files[item.key] = item.val; 185 | } else { 186 | config.data[item.key] = item.val; 187 | } 188 | } 189 | 190 | setTimeout(function () { 191 | if (!FileAPI.hasFlash) { 192 | throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; 193 | } 194 | xhr.__fileApiXHR = FileAPI.upload(config); 195 | }, 1); 196 | } else { 197 | if (this.__origError) { 198 | throw this.__origError; 199 | } 200 | orig.apply(xhr, arguments); 201 | } 202 | }; 203 | }); 204 | window.XMLHttpRequest.__isFileAPIShim = true; 205 | window.FormData = FormData = function () { 206 | return { 207 | append: function (key, val, name) { 208 | if (val.__isFileAPIBlobShim) { 209 | val = val.data[0]; 210 | } 211 | this.data.push({ 212 | key: key, 213 | val: val, 214 | name: name 215 | }); 216 | }, 217 | data: [], 218 | __isFileAPIShim: true 219 | }; 220 | }; 221 | 222 | window.Blob = Blob = function (b) { 223 | return { 224 | data: b, 225 | __isFileAPIBlobShim: true 226 | }; 227 | }; 228 | } 229 | 230 | })(); 231 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/test/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/test/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "private": true, 4 | "dependencies": { 5 | "chai": "~1.8.0", 6 | "mocha": "~1.14.0" 7 | }, 8 | "devDependencies": {} 9 | } 10 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Spec Runner 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/webapp/js/node_modules/ng-file-upload/test/spec/test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | describe('Give it some context', function () { 7 | describe('maybe a bit more context here', function () { 8 | it('should run here few assertions', function () { 9 | 10 | }); 11 | }); 12 | }); 13 | })(); 14 | -------------------------------------------------------------------------------- /src/main/webapp/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js", 3 | "version": "1.0.0", 4 | "description": "Sample Angular / Spring App", 5 | "main": "posSample.js", 6 | "dependencies": { 7 | "angular": "^1.4.7", 8 | "angular-animate": "^1.4.7", 9 | "angular-ui-bootstrap": "^0.14.3", 10 | "ng-file-upload": "^9.1.2" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "author": "Jon Onstott", 17 | "license": "ISC" 18 | } 19 | -------------------------------------------------------------------------------- /src/main/webapp/js/posSample.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic angular setup 3 | * 4 | * @author Jon Onstott 5 | * @since 11/1/2015 6 | */ 7 | 8 | // In a real app, we wouldn't have globals hanging around like this 9 | var baseUrl = "http://localhost:8080/api/"; 10 | 11 | var posModule = angular.module('posApp', ['ui.bootstrap', 'ngFileUpload']); 12 | 13 | // From http://stackoverflow.com/a/17648547/132374 14 | posModule.filter('padWithLeadingZeros', function () { 15 | return function (n, len) { 16 | var num = parseInt(n, 10); 17 | len = parseInt(len, 10); 18 | if (isNaN(num) || isNaN(len)) { 19 | return n; 20 | } 21 | num = '' + num; 22 | while (num.length < len) { 23 | num = '0' + num; 24 | } 25 | return num; 26 | }; 27 | }); -------------------------------------------------------------------------------- /src/main/webapp/js/services/order-persistence.factory.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .factory('orderPersistence', orderPersistence); 5 | 6 | /** 7 | * orderPersistence service 8 | * 9 | * A service for CRUD operations on Orders using REST 10 | */ 11 | orderPersistence.$inject = ['$http', '$q']; 12 | 13 | function orderPersistence($http, $q) { 14 | var service = { 15 | addItemToOrder: addItemToOrder, 16 | assignOrderNumber: assignOrderNumber, 17 | createOrder: createOrder, 18 | recordPayment: recordPayment, 19 | removeItemFromOrder: removeItemFromOrder 20 | }; 21 | return service; 22 | 23 | function addItemToOrder(orderId, itemId) { 24 | $http.post(baseUrl + "orders/" + orderId + "/items?itemId=" + itemId); 25 | } 26 | 27 | function createOrder(initialItem) { 28 | return $q(function (resolve, reject) { 29 | var newOrder = {id: null, orderNumber: null}; 30 | $http.post(baseUrl + "orders", newOrder) 31 | .then(function (response) { 32 | var orderId = response.data; 33 | addItemToOrder(orderId, initialItem.id); 34 | resolve(orderId); 35 | }, function () { 36 | reject("The new order's ID couldn't be retrieved"); 37 | }); 38 | }); 39 | } 40 | 41 | function removeItemFromOrder(orderId, itemId) { 42 | $http.delete(baseUrl + "orders/" + orderId + "/items/" + itemId); 43 | } 44 | 45 | function assignOrderNumber(orderId) { 46 | return $http.get(baseUrl + "orders/" + orderId + "/assignOrderNumber"); 47 | } 48 | 49 | function recordPayment(orderId, amountTendered, changeDue) { 50 | var tenderRecord = { 51 | orderId: orderId, 52 | amountTendered: amountTendered, 53 | changeDue: changeDue 54 | }; 55 | $http.post(baseUrl + "orders/" + orderId + "/tender", tenderRecord); 56 | } 57 | } 58 | })(); 59 | -------------------------------------------------------------------------------- /src/main/webapp/js/services/order-status.factory.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .factory('orderStatus', orderStatus); 5 | 6 | /** 7 | * orderStatus service 8 | */ 9 | orderStatus.$inject = ['$http', 'orderPersistence']; 10 | 11 | function orderStatus($http, orderPersistence) { 12 | // This is set once the order has been created in the backend 13 | var orderId; 14 | var orderNumber; 15 | var salesTaxRate; 16 | 17 | var service = { 18 | addItem: addItem, 19 | editOrder: editOrder, 20 | getGrandTotal: getGrandTotal, 21 | getOrderId: getOrderId, 22 | getOrderNumber: getOrderNumber, 23 | getSalesTax: getSalesTax, 24 | getSubtotal: getSubtotal, 25 | hasOrderNumber: hasOrderNumber, 26 | /** A dictionary of the items in the order; see http://stackoverflow.com/questions/11985863/how-to-use-ng-repeat-for-dictionaries-in-angularjs */ 27 | itemsInOrder: {}, 28 | removeItem: removeItem, 29 | /** When payment is processed, this becomes a JSON object with amountTendered and changeDue */ 30 | paymentResults: {}, 31 | setOrderNumber: setOrderNumber, 32 | startNewOrder: startNewOrder 33 | }; 34 | activate(); 35 | return service; 36 | 37 | function activate() { 38 | $http.get(baseUrl + "settings/salesTaxRate").then(function (response) { 39 | salesTaxRate = response.data; 40 | }); 41 | } 42 | 43 | function startNewOrder() { 44 | orderId = null; 45 | orderNumber = null; 46 | clearItems(); 47 | clearPaymentDetails(); 48 | } 49 | 50 | function editOrder(order) { 51 | orderId = order.id; 52 | orderNumber = order.orderNumber; 53 | 54 | clearItems(); 55 | angular.forEach(order.lineItems, function (lineItem) { 56 | var itemName = lineItem.item.name; 57 | service.itemsInOrder[itemName] = { 58 | item: lineItem.item, 59 | quantity: lineItem.quantity 60 | }; 61 | }); 62 | 63 | if (order.tender) { 64 | // Copy over the amountTendered and changeDue values 65 | angular.extend(service.paymentResults, order.tender); 66 | } else { 67 | clearPaymentDetails(); 68 | } 69 | } 70 | 71 | function getOrderId() { 72 | return orderId; 73 | } 74 | 75 | function hasOrderNumber() { 76 | return orderNumber && isFinite(orderNumber); 77 | } 78 | 79 | function getOrderNumber() { 80 | return orderNumber; 81 | } 82 | 83 | function setOrderNumber(newOrderNumber) { 84 | orderNumber = newOrderNumber; 85 | } 86 | 87 | function addItem(item) { 88 | var itemName = item.name; 89 | 90 | var existingItem = service.itemsInOrder[itemName]; 91 | 92 | var newItem = existingItem || {item: item, quantity: 0}; 93 | newItem.quantity++; 94 | 95 | service.itemsInOrder[itemName] = newItem; 96 | 97 | // Update the backend 98 | if (!orderId) { 99 | orderPersistence.createOrder(item).then(function (id) { 100 | orderId = id; 101 | }); 102 | } else { 103 | orderPersistence.addItemToOrder(orderId, item.id); 104 | } 105 | } 106 | 107 | function removeItem(item) { 108 | delete service.itemsInOrder[item.name]; 109 | orderPersistence.removeItemFromOrder(orderId, item.id); 110 | } 111 | 112 | function clearItems() { 113 | angular.forEach(service.itemsInOrder, function (entry, key) { 114 | delete service.itemsInOrder[key]; 115 | }); 116 | } 117 | 118 | function clearPaymentDetails() { 119 | delete service.paymentResults.amountTendered; 120 | delete service.paymentResults.changeDue; 121 | } 122 | 123 | function getSubtotal() { 124 | var subtotal = 0; 125 | angular.forEach(service.itemsInOrder, function (entry) { 126 | subtotal += entry.quantity * entry.item.price; 127 | }); 128 | return subtotal; 129 | } 130 | 131 | function getSalesTax() { 132 | return getSubtotal() * salesTaxRate; 133 | } 134 | 135 | function getGrandTotal() { 136 | return getSubtotal() + getSalesTax(); 137 | } 138 | } 139 | })(); 140 | -------------------------------------------------------------------------------- /src/main/webapp/js/services/view-manager.factory.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('posApp') 4 | .factory('viewManager', viewManager); 5 | 6 | /** 7 | * viewManager service 8 | */ 9 | viewManager.$inject = []; 10 | function viewManager() { 11 | var service = { 12 | isShowingList: false 13 | }; 14 | return service; 15 | } 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /src/main/webapp/orderList.html: -------------------------------------------------------------------------------- 1 |
4 | 5 |
6 |
7 | 11 | 15 | 19 | 23 |
24 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
StatusTimestampOrder NumberTotal
#{{order.orderNumber | padWithLeadingZeros:3}}
45 | 46 |
47 | No orders to display. Try clicking "All". 48 |
49 |
50 | 51 |
-------------------------------------------------------------------------------- /src/main/webapp/orderSidebar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | New Order 5 | 6 | 7 | Order #{{getOrderNumber() | padWithLeadingZeros:3}} 8 | 9 |
10 | 11 | 12 |
13 |
14 |
18 | 19 |
{{entry.quantity}}
20 |
{{name}}
21 |
{{entry.item.price | currency}}
22 |
23 |
24 |
25 |
26 |
Subtotal:
27 |
28 |
29 | 30 |
31 |
Sales Tax:
32 |
33 |
34 | 35 |
36 |
Grand Total:
37 |
38 |
39 |
40 |
42 |
43 |
Amount tendered:
44 |
46 |
47 |
48 |
Change due:
49 |
50 |
51 |
52 |
53 | 54 |
55 | 61 | 67 |
68 |
-------------------------------------------------------------------------------- /src/main/webapp/tenderPaymentDialog.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 31 | 32 | --------------------------------------------------------------------------------