Categories
Programming Projects

List of Quirks when using Spring Framework and Kotlin

I’m not used to the JVM environment. Usually I use NodeJS, Ruby with Rails or PHP with Laravel. But after awhile, I find these dynamic programming environment quite problematic, especially on a large project. Rails is simply too magical, it is dark magic. Sure, it have a nice syntax, but debugging them is quite a hell and no type safety means you really need to unit test your software. PHP and Laravel is not that bad actually, but when the things that makes it nice came from statically typed language, like type hints and IoC container, why not just use a true statically typed language? NodeJS have quite a lot of things. You can have type safety with typescript. You probably can get an IoC container… if you want to use them. But the whole NodeJS environment is way too immature and changes too quickly making it feels quite brittle. Update a package and the whole thing could collapse. Not too mention typescript support really depends on the library maintainer. Its the same case for flowtype too. Except I had worse luck with it.

So, when you want to use a statically typed programming language to make a web application, two things came to mind: C# and Java. Other languages are too not-mainstream enough. With C#, you have .NET MVC and with Java, you have Spring, Jersey and Play framework. The open source version of .NET which is .NET Core is simply too immature. So whats left is Java. Java is quite tedious, but luckily, we have Kotlin, which is another programming language running on top of the JVM with very high interoperability with Java. In the choice of framework, Play framework utilize Akka which is way too different than conventional application. Jersey is actually quite nice, but it does not have a built in integration with other things. Spring is the most famous of them all, so its not that hard to choose, its just that it could be overwhelming.

First of all, what is Spring framework? You need to get this right real quick, because the libraries built around it is quite a lot. At its core, the Spring framework by itself is only an IoC container. Spring MVC is a web framework that have the Model-View-Controller architecture that we want. Spring Data is an umbrella project for data access related things and among those is Spring Data JPA which provides integration with JPA (Java Persistence API) which is a java specification for ORM (Object Relational Model). Spring Data JPA utilizes Hibernate which is a JPA implementation.

Now do you see what I mean by overwhelming? All of these libraries are configured by declaring a Configurer bean. A bean is basically a fancy way of saying class/object with specific structure. You can also configure through XML, but that is old school technique which result is a huge mess of XML. The new way is too declare a Configurer class and configure it with code. In general you’ll probably use a ConfigurerAdapter instead, because Configurer is an interface and you don’t want to configure everything. But still, that is a lot of configuration. Which is why they created Spring Boot, another project which is basically a collection of configurations of these libraries. Spring Boot will detect if you include these library and will automatically apply a default reasonable configuration. ‘reasonable’ does not mean convenient, or in some sense even reasonable. I guess a better word is ‘compatible’ or ‘safe’. Still, it is too invaluable that you probably should google ‘Spring Boot’ instead of ‘Spring Framework’. But what if you want to adjust some parameters of some library? Well, it could be that Spring Boot have already enabled the use of configuration files. Which by default is usually application.properties. But you can also use YAML. I’m not exactly sure the file name for that. But what are the settings? Here comes the problem with Spring framework in general: the only documentation is its user guide/reference and those references are very long. That, or you go through the source code of Spring Boot or the respective library, which are Java code, which are very long.

Once you get through those long and tedious readings, assuming nothing goes wrong, you get the ability to:

  • Create an MVC controller by annotating arbitrary class, with the base path and so on.
  • Declare a a bean which is an interface that extends CrudRepository<Model, Int> where Model is your arbitrary POJO (Plain Old Java Object) that have certain annotations that declare it as a model. And inject it to your arbitrary other bean (like your controller), gaining a free implementation of your interface that interface with your database.
  • Include liquibase in your gradle, then just declare your migration in resources/db.changelog/db.changelog-master.yaml.
  • Ability to map http body to arbitrary object, effectively parsing JSON request to a POJO, with a single annotation.
  • Return a POJO from your controller method, and it will automatically convert it to JSON.
  • Annotate your POJO with java.validation annotations and annotate your POJO controller parameters with @Valid and it will throw error if the JSON send does not validate.
  • Ability to annotate your bean method with a security expression, and it will throw error if it does not pass.
  • Java’s plain simple and conventional generic and inheritance model.
  • Probably other things too.

If that sounds quite magical, well it is. Maybe too magical, that if somethings goes wrong, you don’t know what happen. Good thing its all mainly Java code, so you got nice IDE support for debugging. But its a whole lot of Java code. A whole lot.

Of course, its not all good. Some of it is quite bad actually. The template is JSP which I’m not a big fan off. You can’t easily rollback your database. I use it mainly for a rest controller. Unfortunately in that regard, Jersey brings more things out of the box. When a validation error happen, it does not report it as a nice JSON. Jersey does that nicely. When a resource is not found and so it fails to bind the resource as parameter, it simply pass an empty resource. Jersey return a nice 404. And of course, when something goes wrong, I’ll take a day or two to find the problem. So here is a list of problem/quicks I found so far. I’ll probably update it as I found more issues.

It is asking for a password, some csrf validation error? And CORS issues.

By default, the spring boot configured spring security will ask for password, which is randomly generated. CSRF protection is enabled, not really useful for REST. Also, CORS will need to be allowed. So I used these config to overcome those issues.


import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
import javax.inject.Named

/**
 * Created by amirul on 31/05/2017.
 */
@Configuration
@EnableWebSecurity
class WebSecurityConfig: WebSecurityConfigurerAdapter() {
	override fun configure(http: HttpSecurity) {
		http
			.cors().and()
			.csrf().disable()
			.authorizeRequests()
			.anyRequest().permitAll().and()
	}

	@Bean
	fun corsConfigurationSource(): CorsConfigurationSource {
		val configuration = CorsConfiguration();
		configuration.allowedOrigins = listOf("*");
		configuration.allowedMethods = listOf("GET","POST" ,"PATCH");
		configuration.allowedHeaders = listOf("*");
		val source = UrlBasedCorsConfigurationSource();
		source.registerCorsConfiguration("/**", configuration);
		return source;
	}
}

Model Validation does not work with kotlin.

If you use a data class with the constructor arguments, make sure you annotate the field instead of the getter/setter with something like @field:NotBlank.

In a one to many column relationship, on save, the child’s foreign key is not set, and I’ve specify it in database as non-null, so it will crash.

In a a JoinColumn annotation, make sure you specify nullable = false, like this @JoinColumn(name= "parent_id", nullable = false)

In a one to many relationship, if I remove the child item from the array from the parent object and save the parent, the removed child is not removed in the database.

In the @OneToMany annotation, make sure to set orphanRemoval to true.

I’m getting `org.hibernate.HibernateException: A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance:`.

Yea… the collection in the entity is pretty special. So you can’t just assign them and hope the complex datamapper technique to work. So don’t replace the collection object itself. Call clear and addAll instead.

I’m getting `org.hibernate.AnnotationException: Collection has neither generic type or OneToMany.targetEntity()`.

Try not using Kotlin’s immutable List. Which is the default list. Use MutableList instead. Yeah, I know Hibernate does not play well with immutable data types at all.

Categories
Projects

Automatic IIUM Schedule Formatter/Maker : End of life announcement

The Automatic IIUM Schedule Formatter/Semi-Automatic IIUM Schedule Maker is probably the most used application that I’ve ever made. That is impressive or disappointing depending on how you look at it. For some reason (probably because there is a problem to be fixed and hence, a demand) quite a lot of people use it. However, because I’m no longer studying in IIUM, I’m no longer one of the people who use it. And because of that, there is effectively no reason for me to support it anymore. Therefore, I’m declaring it life as effectively ending. I make no guarantee that it will stay online.

capture2
Here, we see a very ‘seasonal’ usage pattern.

For your information, the application itself did not earn me any money. It does, get me some nice portfolio. Quite a lot of people asked me “Why don’t you sell it to IIUM?”or “Why don’t you put ads on it?”. Well, the short answer is, “That sounds complicated, I’m too lazy for that.”. The long answer is, I don’t want it to ‘matters’ too much and I don’t want to get some ‘feature request’ from some guy, and I don’t want to get into trouble if someone found a bug. It is really, a ‘pet’ project, and I prefer to keep it that way. Plus, asking money from IIUM for something that should do in the first place does not sounds like a very good idea. I sense a lot of bureaucratic issues on that path.

Because it is running on my private server which also host other applications, like this blog, it does not really have a hosting cost. I kinda ‘piggyback’ it on other application. Plus, it DOES automatically scrape data from IIUM server every Sunday (But it turns out, sometimes IIUM change it last minute… after Sunday). Therefore, I don’t really need to do anything for it to function normally. However, every year, I do need to set which year is available, which involve editing about… 2-4 line of code.

Selection_016
This is its original purpose

As I’m too lazy to put any further significant improvement, I’ve open-sourced it several years ago. It seems that not many people know that you can get the source code and see how it works. Probably because I did not put a “Fork me on Github” banner before, which I just did yesterday. I hope that some other geeks can learn from its source code, and create their own version, or better yet, create a totally new one, as the code is quite ugly. The code originated nearly 5 years ago when I was still in CFS. Back then, the popular ‘schedule maker’ part did not exist, only the ‘schedule formatter’ which works on CFS’s slip exist. It was originally designed to run on Google App Engine as I don’t have money for my own server back then. Because of that, the code is pretty much some hacks over some other hacks. Therefore, I recommend that those who want to make a similar system to build one from ground up. You can also, just host it yourself. In fact, when the new I-Maalum system was announced, I was expecting them to also integrate a similar solution, or even just straight up copy-paste it. That did not happen.

So when will it go down? Who knows. The thing is, removing it from the server also take some work, and I’m too lazy for that. I’ll probably just leave it there. If my server crash, I may forget to start the application manually (which happened a few month ago). Once it is up, It does not really need much maintenance, so it will probably stay up for some time. However, for the 2017/2018 session, I’ll need to add that to the server’s config and I can’t guarantee that. Chances are, I’ll forget about it. In fact, I did forget about the 2016/2017 session until someone mentioned it to me. That is assuming no major changes in the IIUM system. If IIUM decided to change the format of their course list page, the scraper will not longer work and it will no longer give you updated course list.

capture
Fork me!

In conclusion, tell your geek friend to make a similar system. I’m graduating, I don’t even know when is the prereg, start of semester and such. I thank you all for your kind support and encouragement throughout these years, I appreciate it. It is truly a satisfying feeling to know that your work helps a lot of people, even if it does not get you money, and the code is very ugly. Also, if you need to contact me, email me directly at asdacap@gmail.com. Please do not PM me through facebook, as facebook have the ‘message request’ filter. Sorry, if I did not reply to your facebook message if you messaged me a few month ago because I did not realize it, and replying now would be very awkward. That is all from me. Goodbye and Assalamualaikum.

ps: Try reading this article again, but everytime you find the word ‘lazy’, change it to ‘busy’. Its interesting how changing the meaning of a single word can change a lot of perspective. Also its not far from the truth too. 

Categories
Programming Projects

Thru Earth Position

Assalamualaikum everyone,

People in US usually said that “China is halfway around the world”, which means that, (given that the earth is round) if you want to point where where is China, you would point it straight underground. I wonder, what about other part of the world? Do North pole also go down? How far down? So I made an app for it called “Thru Earth Position”.

hires
A poor man’s logo.

Why “Thru Earth Position”? The answer is mainly because I can’t figure out a better name. It was originally an augmented reality experiment on how to map location on earth to the screen. Or in another word, I want to move around the screen and the screen will show what location is in front of me. It work, and I thought it was cool, so I made another one, with a 3D globe-like interface. For some reason, the addition of 3D globe visual kinda reduce the feel of AR-ness. 

Screenshot_2016-08-15-15-24-47
Something feels off…

That, or mainly because I’m generally a bad designer. The app also shows the distance from your current position. It is relatively simple, with some settings do show or hide elements. It does not use the internet aside from google maps and geocoding to fetch the location name.

The app is actually my first ‘kinda-proper’ mobile application. It is made using libgdx(an OpenGL game framework) and Kotlin(a Java compatible programming language). So, it is kinda an experiment on “making an android app using Kotlin and libgdx with some OpenGL stuff”. It is available on google play store. Which means, it is also another experiment on “what happen when you make a relatively simple app and publish it”. You can find it here. Try it out, and let me know what you think about it.

Categories
Programming Projects

Rails-Devise-Warden Token Authentication + Android’s Volley

Hi, recently I’m trying to make an android app that uses a rails server. I used to code REST server before, but that is for javascript apps. Not android. And now that I’m trying to do so, I found a problem. Whenever the devise authenticate_user! failed and it throw 401 to the android app, Volley complain that some form of challenge key is missing. It work fine with a browser and it responded with an error in json form. This is the normal Devise behavior, which seems to also have support for api server. Because of the error in Volley, I can’t get the JSON that contain the error in order to show the message.

For the impatient one, the solution is this:
[ruby]

Devise::FailureApp #Autoload it

##
# Patch failure app so that the WWW-Authenticate message is sent regardless if using http_authenticatable.
class Devise::FailureApp < ActionController::Metal
alias :old_http_auth :http_auth
def http_auth
old_http_auth
self.headers["WWW-Authenticate"] = %(Basic realm=#{Devise.http_authentication_realm.inspect})
end
end

[/ruby]

Put this in an initializer. Another thing is that, make sure that Volley tells Rails that it is accepting a json response, through HTTP Headers or ‘.json’ postfix.

The reason for the Volley error is that, when an HTTP server respond with HTTP 401, it is expected to respond with “WWW-Authenticate” header. The spec indicate that it MUST respond so. The problem is, the browser treat this header as an indicator to show user an HTTP Basic Authentication form, which is not always what we want. We just want to respond with 401 to indicate that the user is unauthorized for that particular resource. We can’t really use 403 because that means, Forbidden, a different meaning. Showing a 200 with an error message/json just feels too hackish for an api server. Because of this, devise did not send the header if the model did not enable http basic authentication.

By default, Devise sent out a 401 when it detect that the request is not of a ‘navigation’ format. By default that is [*,:html], which as far as I understand, every format and html. Or in another word, everything. For those of you who have problem with setting up Devise for an api server, change this to [:html] and make sure your client application properly indicate its accepted format. Using this, which basically means nothing else but the patch above, my api server seems to be working for now with no error on Volley (aside from authentication error, but that is expected). If the user manually send a request there from a browser, it will redirect to a login page.

Also, if you intend to use token authentication, checkout the devise-token_authentication gem. Devise developers for some reason (which seems to be security) removed the token authentication strategy from devise. This gem is a fork of the token authentication strategy.

Categories
Programming Projects Uncategorized

Introducing, String2Regex

Assalamualaikum everyone. In this post, I’ll show you a (relatively) new project which I’ve created in about a week or two. It seems to be useful for greater good and so I’ve released the code under the MIT license. The code is available here

String2regex is basically a clone of txt2re.com but run entirely on client side and not as full featured. The concept is, given a sample string, generate a regular expression to match that string based on user’s selection of string groups. If you don’t know what is regular expression, you are probably not a programmer and this is effectively useless for you. If you are a programmer but you do not know what is regular expression, then you are missing out. If you know what is a regular expression, check it our here and you’ll understand what does it do. Screnshot

One significance of this application for me is that it is the first (I think) client side application I made that have some kind of automated testing. It is also the my first client side application that is made ground up with some nifty javascript tools such as bower and grunt. Styler and IIUMSchedule both uses grunt for concatenation purpose but not originally. In another word, String2Re is currently the most… what is the word?… decent? proper? … yes, proper web application I’ve ever made right now. 

This is largely due to some automated testing build with it. The testing is not perfect, as it does not test it entirely and it does not test the view, just the controller logic. However the controller logic is quite complicated (not really) so having some test for that reassure me that it will not crash easily. 

Categories
Article Programming Projects

Perbezaan antara Python, Ruby dan PHP.

Assalamualaikum semua, apa khabar, dalam post ini, saya akan mencabar diri saya untuk memperjelaskan perbezaan antara Python, Ruby dan PHP dalam kontext pengatucaraan laman sesawang. Cabaran sebenar saya dalam post ini sebenarnya adalah menggunakan bahasa melayu sebanyak yang mungkin. Sesuatu yang sukar dilakukan memandangkan ini adalah bidang perkomputeran yang mana kebanyakan dokumentasi adalah dalam bahasa Inggeris. 

Categories
Personal Projects

Thank you for using SemiAutomatic IIUM Schedule Maker

Assalamualaikum everyone. How are you guys? First of all I would like to say thank you very much for using my newest application SemiAutomatic IIUM Schedule Maker. I expect it to get significant traffic, but not this much. For the record, between 19 April 2014 to today 27 April 2014, the application got 9089 visit or 16928 pageviews, 80% of that is from schedulemaker, 10% from scheduleformatter. In comparison, from january 2012 (the start of the application) to 19 April 2014, it received 9335 visits or 19128 pageviews. Within one week, schedulemaker almost surpassed the amount of traffic generated by scheduleformatter for two whole years. 

Selection_017

It is THAT significant. In fact in the first hour after my friend share it, the server’s cpu peaked at 100% for almost half hour. The traffic steadily decreases after that. One interesting fact is that for every day on prereg, the traffic peaked at 11 PM. Assuming that IIUM Student start sorting their schedule at 11 PM is one thing, but actually having the data to say so is a totally different feel.

Frankly I never though that the tools I make because I’m too lazy sort my schedule or make a timetable would get this much traffic, let alone my website with the highest reception. I mean, the number of IIUM student is approximately 20000, google analytic say that there are 5240 users. Lets assume some of them also use their phone/tablet, so the actual number of user is about 4000. So one every five student in IIUM use this application. Someone actually use this. 

One again I would like to say thank you very much for using the application. This will look brilliantly in my CV. And thank you to all my friends who share the application. Like usual,  if I’m the one who share it, it does not work, but for some reason, if someone else share it, it get hundreds of like. It happened with schedule formatter , it happened with this one and it will happen again in the future. That’s it for now, good bye and Assalamualaikum. 

Categories
Projects

SemiAutomatic IIUM Schedule Maker!

Assalamualaikum everyone, how are you? So, lets get straight to the point. The purpose of this post is to publicly kinda-announce SemiAutomatic IIUM Schedule Maker. Given that it does not have an official readme page, I cannot confidently say that I’m releasing it to the wild. However, given that prereg is next week, and I don’t feel like making an official readme page any time soon, I’m announcing it now in this post. Also, this post will act like a readme page for now. 

So what is SemiAutomatic IIUM Schedule Maker? Basically it is a single web page application that helps you plan/sort your schedule for the next semester. Given that I miss several subject already, I think this should be very useful. How does it helps you? Well…. it:

  • Show your schedule in a day-to-day table like in Automatic IIUM Schedule Formatter in a preview.
  • When you hover the add/replace section button, it show your section as highlighted in the schedule preview.  
  • When a section collide with another section of another subject, it will show that this section collide with the other section. 
  • It has a generator function in which you select all the subject that you want to enroll, then it will list down all possible section combination. Then you just select one of them, and the changes is applied. 
  • It does NOT automatically register the subject. It is just an aid to help you sort your schedule. Don’t blame me if you did not prereg the subject.

How to use it. 

To use it, first go to http://iiumschedule.asdacap.com/schedulemaker/ which is the schedule maker. The first thing you see is that it ask for the session of the schedule and student type. Just enter the session for the schedule and the student type.

plasma-desktop_008

 

Click submit, and it will enter a loading page where it will fetch the subjects for that particular semester for that particular student type. Then you will see this page. 

SemiAutomatic IIUM Schedule Maker

The page is divided into two part. The left part is the preview of current schedule, with the added section as a table. Click on the big red button to remove the section from current schedule. There is two bottom button, the Schedule Generator and the Schedule Formatter which will be explained later. The right section will show all the subject available.

Selection_011

 

Keep in mind that the subject list does not necessarily reflect the current subject available in IIUM. The server will attempt to update the list every week. However it may fail to do so. Just now I tried to update it manually. However, it seems that IIUM server is currently under heavy load and does not response to a request. 

Selection_012

The right panel has a list of kuliyyah. Click on it to filter down the subject list to subject on that particular kulyyah unly. Click on the kuliyyah again to cancel the filter. It also have a search input on top which you can use to search for subject. Click on a particular subject to fetch and show list of section for that particular subject. The section list have an add button. Hover on it to show preview on where it is in the schedule. Click on it to add the section. If the section collide with another section, the add button will not show up. 

Schedule Generator

plasma-desktop_013

Click on the schedule generator to open the schedule generator page. On the left of the generator is also a subject list. However you cannot select a section over here. You only select subject. Several of them, which is which subject you want to pick. After you select the subject, it will generate all possible section combination of the subjects. In another word, several possible schedules with no section collision.

Selection_014

Beware, the application still need to fetch the section list from the server. So wait a few second for all combination to appear. The top button list down the selected subject and the number of section fetched in the black circle. Pick the schedule that you like using the ‘Use Schedule’ button, and it will go back to previous page.

Schedule Formatter

plasma-desktop_015

After you are done with picking section, you can save your schedule through Automatic IIUM Schedule Formatter. Click on the “Schedule Formatter” button to open another page. This page will ask you to input several more information that is needed by the formatter. Just fill it up or leave empty. Click “Save Data”, then it will show notice of saving the data, then another button will appear that say ‘Go to Automatic IIUM Schedule Formatter’. Click on it to open schedule formatter.

Selection_016

That is It

So that is it. It basically does that. I probably should mention about the list limitation I put for performance reason. But its not a big deal. By the way, the whole source code is now available at github. So you can now see the source code. Which means that now, if you have a problem with this thing, or if you want a new feature to be implemented you have two choice. First, the less recommended one, you can email me at asdacap@gmail.com, then wait indefinitely. Or a (much) better alternative, fork the code repository, do the modification yourself, then send a pull request. Anyway, that’s it for now. Good bye and Assalamualaikum. 

PS: You can now share your schedule using a link right above the schedule preview. Just share the link with your friend. The link change every time you add or remove section. 

Categories
Projects

We are moving

Assalamualaikum everyone. How are you? Anyway, I have a relatively important announcement to make.

Today at, 24 January 2014, Automatic IIUM Schedule Formatter is officially moved from iiumschedule.appspot.com to iiumschedule.asdacap.com .

Or in another word, I changed the domain. The reason why I change the domain is because I changed the server. Why I changed the server? Because of various technical limitation with Google App Engine. So I recently bought a hosting, (not just for iiumschedule), and recently had fully(I think) converted the automatic iiumschedule formatter for it. Most of you will not feel a thing because the old bookmark will automatically redirect to the new domain. If it does not, try to clear your browser cache. However, if you go to iiumschedule.appspot.com , you will see a notice of the domain change.

Aside from that, now the resulting timetable will also show your lecturer’s name. So you will never forget your lecturer’s name again. This feature is made feasible with the use of the new hosting.

iiumschedul

Unfortunately, this only work for main campus student. CFS student will see a blank column. You can hide the column in the ‘settings’ tab. Also, this feature require me to manually upload the section data which is scraped from IIUM’s own website. Sometimes, new section data is not available in time, and sometime the lecturer’s name is ‘to be determined’. You get the point, so if you have some section with incorrect lecturer’s name, try IIUM portal first to see if the name is correct, if it is correct in IIUM portal, then you may email me at asdacap@gmail.com for inquiry, chat, request and such. Talk about inquiry, I have not test the error reporting tool fully, and currently I have no way of getting the error report other then directly from the database through SQL. So for now, the error reporting may not work properly yet. 

The change of domain also means that all your previous facebook likes are irrelevant and it now calls for a new like. So please go to the page, and like it. I guess this is the end of this announcement. Thank you for all your support. Assalamualaikum and bye.

Categories
Projects

I declare thee V6! Now with support for Main Campus User

Assalamualaikum, and hello to everyone. Today marks a new version for the Automatic IIUM Schedule Formatter, now version 6.

Among new feature include:

  • Support for main campus user. So now it work on both CFS’s CRS and also IIUM Main Campus’s Confirmation Slip. This involves:
    • Modifying the code to accept main campus matric no.
    • Modify the code to compensate for the use of minute in the schedule, previously unavailable in the CFS’s CRS.
    • Modify the code to compensate for saturday and sunday class not to mention some class is at night, so the start time and end time of the schedule is automatically detected instead of the old 8 am to 5 pm fixed range.
  • A new tab called “settings” that allow you to specify settings previously not possible in Styler. Among them are:
    • Ability to select which day will be shown.
    • Ability to turn on or off minute align tweak which fix the minute alignment of the schedule.
    • And also, as previously available in Styler, the ability to show or hide the personal details and also course table.
  • You can now make your own color palette in Styler.
  • You can now make a report even if no error occur.
  • And also, a new look.
Visit the Automatic IIUM Schedule Formatter home page for information on how to automatically convert your Confirmation Slip/CRS into a day-to-day schedule as below.
PS: Please open your Confirmation Slip in a new tab. Otherwise it won’t work.