czwartek, 29 listopada 2012

how about state machine in html widget?

I've got the task to create complicated widget to select date. When I crystallized in my minds how it must behave, then I saw many if/then conditions. When You are in a developing process it seems to be not so scary, but when You have to change it, or fix after few weeks, usually it is a bloody mess.

I stared wonder if there is a better way to handle this and here is a way (I think)  that could help. Use a state machine concept. We can create some instance variables to save state and relaying on them build all widget logic (it is crappy) like that:
# no bueno
class DateWidget
constructor: ->
@firstLevel = true
@fromDate = null
@toDate = null
fillFromDate: ->
if @firstLevel
#initialize fromDate and toDate day selects
else if @fromDate
#do some
else if @toDate
#do something else


The real state machine is something where you define available states, and events which changes the state. Pretty good lib to handle this is:
https://github.com/jakesgordon/javascript-state-machine . It allows You to define for one event many routes, depending on your actual state, which suits this state diagram. Also there are callback on enter/leave state and before/after fire event. To event you can pass message(an argument), so you can treat it as normal method, which do some extra stuff.


First of all is the pen and paper,  you need to draw simple graph for your control. Circles are the states and lines are the events, and that's all. When you properly design your graph, save this to code:

initial: 'firstLevel'
events: [
{name: 'fillFrom', from: 'firstLevel', to: 'fromFilled'},
{name: 'fillFrom', from: 'toFilled', to: 'fromToFilled'},
{name: 'fillTo', from: 'fromFilled', to: 'fromToFilled'},
{name: 'cleanFrom', from: 'fromFilled', to: 'firstLevel'},
{name: 'cleanFrom', from: 'fromToFilled', to: 'toFilled'},
{name: 'cleanTo', from: 'toFilled', to: 'firstLevel'}
{name: 'cleanTo', from: 'fromToFilled', to: 'fromFilled'}
]
Define some callbacks on enter/leave state. Also useful are callbacks on fire specific event. To each callback will be passed arguments which allow You to do specific code basing on previous/next state, and argument passed explicitly to event. Now bind dom events and call events on state machine.
class views.search_filters.DateFilter extends views.AbstractRenderedComponent
constructor: ->
@createStateMachine()
@configure()
..
configure: ->
@configureFromToItems()
@element.find('.group-change').on 'click', (event) => @showMonthSelector()
@element.find('.month-selector span').on 'click', (event) => @selectMonth($(event.currentTarget))
configureFromToItems: ->
@element.find('.from-to').find('.notify-item .remove').on 'click', (event) =>
event.stopPropagation()
item = $(event.currentTarget).closest('.notify-item')
if item.attr('data-bind') == 'date_from'
@stateMachine.cleanFrom()
else
@stateMachine.cleanTo()
@element.find('.from-to').find('.notify-item').on 'click', (event) =>
@showDaySelector($(event.currentTarget))
@daysSlider = new views.search_filters.RangeWindowSlider @element.find('.from-to .range'),
onChange: (days) =>
@stateMachine.slideDate(days)
configureItem: (item) ->
item.on 'click', (event) =>
if _.isEmpty $(event.currentTarget).attr('data-value')
@showMonthSelector()
else
@showDaySelector($(event.currentTarget))
...
selectDay: (date, daySelector) ->
...
if @stateMachine.current == 'firstLevel'
@stateMachine.fillFrom new Date(date)
else
currentItem = daySelector.closest('.notify-item')
if currentItem.attr('data-bind') == 'date_from'
@stateMachine.fillFrom new Date(date)
else
@stateMachine.fillTo new Date(date)
createStateMachine: ->
@stateMachine = StateMachine.create
initial: 'firstLevel'
events: [
{name: 'fillFrom', from: 'firstLevel', to: 'fromFilled'},
{name: 'fillFrom', from: 'toFilled', to: 'fromToFilled'},
{name: 'fillTo', from: 'fromFilled', to: 'fromToFilled'},
{name: 'cleanFrom', from: 'fromFilled', to: 'firstLevel'},
{name: 'cleanFrom', from: 'fromToFilled', to: 'toFilled'},
{name: 'cleanTo', from: 'toFilled', to: 'firstLevel'}
{name: 'cleanTo', from: 'fromToFilled', to: 'fromFilled'}
{name: 'slideDate', from: 'fromFilled', to: 'fromToFilled'}
{name: 'slideDate', from: 'toFilled', to: 'fromToFilled'}
{name: 'slideDate', from: 'fromToFilled', to: 'fromToFilled'}
#on self
{name: 'fillFrom', from: 'fromToFilled', to: 'fromToFilled'},
{name: 'fillTo', from: 'fromToFilled', to: 'fromToFilled'},
{name: 'fillFrom', from: 'fromFilled', to: 'fromFilled'},
{name: 'fillTo', from: 'toFilled', to: 'toFilled'},
]
callbacks:
#callbacks for states
onfirstLevel: (event, from, to) =>
@cleanItems() if from != 'none'
@resetMonthSelector()
@element.find('.items').show()
@element.find('.from-to').hide()
item = @addItem()
@setValueOnItem(item)
@activateMonthSelecting()
onleavefirstLevel: =>
@cleanItems()
@showFromAndTo()
@showSlider()
@deactivateMonthSelecting()
onfromFilled: =>
@daysSlider.activate()
@daysSlider.reset()
@setValueOnItem @element.find('.from-to [data-bind="date_to"]')
@toDate = null
ontoFilled: =>
@daysSlider.deactivate()
@daysSlider.reset()
@setValueOnItem @element.find('.from-to [data-bind="date_from"]')
@fromDate = null
onfromToFilled: (event, from, to) =>
@daysSlider.activate()
#callbacks for events
onafterfillFrom: (event, fromState, toState, date) =>
@fromDate = date
@setValueOnItem @fromItem, @fromDate, $.datepicker.formatDate('d MM yy', date)
switch fromState
when 'fromToFilled' then @stateMachine.slideDate(@daysSlider.currentValue)
when 'toFilled' then @stateMachine.cleanTo()
onafterfillTo: (event, fromState, toState, date) =>
@toDate = date
@setValueOnItem @toItem, @toDate, $.datepicker.formatDate('d MM yy', date)
if toState == 'fromToFilled'
@daysSlider.setValue(@calculateDaysBeetwen(@fromDate, @toDate))
onafterslideDate: (event, from, to, days) =>
date = new Date(@fromDate.getTime() + days*views.search_filters.DateFilter.DAY)
@toDate = date
@setValueOnItem @toItem, @toDate, $.datepicker.formatDate('d MM yy', date)
onchangestate: (event, from, to) =>
console.log("event: #{event}, from: #{from}, to: #{to}")
...



Further? OOP design of this example is probably not best. Operation explicitly on view should be in one class and flow in other.

piątek, 16 listopada 2012

windowSlider - JQuery UI widget

Here is modified slider widget from jquery ui - https://github.com/piotrze/window-slider. The new feature is that we can set totalMin and totalMax values. When slider reach end of  window, then window is moved(increase or decrease). Just checkout bellow:

piątek, 9 listopada 2012

setup rails with mongo rspec jasmine

If You wonder how to create new rails app with some fancy stuff, such mongo, haml, rspec, coffeescript, jasmine here a quick recipe:
#installing rails
rails new app_name --skip-active-record
#generators
rails g rspec:install
rails g mongoid:config
rails g jasmine:install
rake db:create
#cleanup
rm -rf test/
rm public/index.html
mv app/views/layouts/application.html.erb app/views/layouts/application.html.haml
#spec.css
/*
*= require application
*/
#spec/javascripts/spec.js
//= require application
//= require_tree ./helpers/.
//= require_tree .



Resources:
http://www.mongodb.org/display/DOCS/Rails+3+-+Getting+Started
https://github.com/rspec/rspec-rails
https://github.com/pivotal/jasmine-gem

środa, 7 listopada 2012

Html blur effect

Fakeblur - I've released simple plugin which allows to imitate blur effect like in windows aero. It in inspired by blurjs.com. I decided to remove preparing blurred image in browaser. It can generate such image only basing on one html element with defined background. For me better way is to prepare blurred background mysefl rather than counting on user turbo pc.
My fork is here: https://github.com/piotrze/fakeblur

piątek, 20 lipca 2012

So now you want try again with frontend?

Those year with ie6 and craziness, when You decided to do not touch fronted never again and become "proud backend developer" .

Now we want to write single page application so anyway we have to touch css. You think in new century it will be easy, you head about html5 with those pretty logo and any browser will support it. I did.

New css properties transform and transition are really nice. We can resize, move, rotate, scale element with nice animation, using only css. But it won't work on ie.... again. Why they can't take a short release path as chorome/ff? Yet they have windows update? Ie has support for transform but without transition property it is useless for animations.
I've found working plugin for jquery to backport that features for ie9/8 - transform. It allow you to use scale, rotate, transform in .animation(). In normal browsers it will use transition and in IE it will use directx filters.

So now my first design of animation is in css(under chrome, due to debugger allow me changing value of css parameter using just arrow up/down. It really helps.). When it's nice and smooth then i do evil if isIE() and split my code for two cases. (In theory that plugin should do that stuff for me.... but in my particular case animation generated from plugin looks good only on IE)
if @isIE(9)
element.animate rotate: '10deg', translate: ['10px', '-15px']
element.animate rotate: '0deg', translate: ['-30px', '80px'], scale: 1
else
element.addClass('hidden')
element.addClass 'showed'

So whats next? IE10 will be released maybe in this year, but after looking for some usage browsers version  charts over internet, it's getting sad. Current IE9 still doesn't sweep previous version.
What is your option? If You are just start creating new app, and it is not some bank stuff, drop ie8 support, at least animations. When Your application will be released, ie8 will be dying browser.

piątek, 6 lipca 2012

Rspec helper for sign in

After I've changed sign in logic, to use only facebook authorization my helper to sign in user in tests crashes. I used omniauth-facebook gem to handle authorization and clearance as legacy gem.
I've tried many times handle that problem.

  1. Easiest way is click and fill, but that means you need connect to fb in test. We can handle that with VCR, which will mock your fb requests. Unlucky  on every test we have different user id, so recorded request will no match.
  2.  Mock everything !!! Yeah, I thought I'll mock essential methods in application controller, current_user, signed_in? etc. In rspec we use rspec-mock, which has similar api as mocha
    #spec/support/prefork/request_helpers.rb
    module RequestHelpers
    def sign_in
    controllers_to_mock.each do |controller|
    controller.any_instance.stub(:current_user).and_return(user)
    controller.any_instance.stub(:signed_in?).and_return(true)
    end
    ensure_signed_in
    end
    def sign_out
    controllers_to_mock.each do |controller|
    controller.any_instance.unstub(:current_user)
    controller.any_instance.unstub(:signed_in?)
    end
    end
    private
    def controllers_to_mock
    ApplicationController.subclasses[3..-1]
    end
    end
    This was little ugly, but it doesn't works. Rspec-mock has some bug with mocking methods in base class using any_instance.
  3. The final solution was already designed in gem omiauth: 
    #spec/support/prefork/request_helpers.rb
    module RequestHelpers
    def sign_in_as(user, password = "password")
    OmniAuth.config.add_mock(:facebook, {"uid" => user.facebook_uid})
    visit root_path
    click_link "Sign in"
    ensure_signed_in
    end
    def ensure_signed_in
    visit root_path
    wait_until {page.should have_content "Sign out"}
    end
    end



wtorek, 26 czerwca 2012

hello world

Only editor crash tests. Do not read.

package lib.reports;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JExcelApiExporter;
import net.sf.jasperreports.engine.JRExporter;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperCompileManager;
public class BaseJasperReport {
//here should be jrxml and jasper files,
//you can generate them with iReports(I'm using netbeans plugin)
// http://jasperforge.org/projects/ireport
static String REPORT_DEFINITION_PATH = "./app/reports/";
public static InputStream generateReport(String reportDefFile, Map reportParams) {
OutputStream os = new ByteArrayOutputStream();
try {
String compiledFile = REPORT_DEFINITION_PATH + reportDefFile + ".jasper";
JasperCompileManager.compileReportToFile(REPORT_DEFINITION_PATH + reportDefFile + ".jrxml", compiledFile);
JasperPrint jrprint = JasperFillManager.fillReport(compiledFile, reportParams, play.db.DB.getConnection());
JRExporter exporter = new JExcelApiExporter();
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, os);
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jrprint);
exporter.exportReport();
} catch (Exception e) {
e.printStackTrace();
}
return new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
}
}
require:
- play
- net.sf.jasperreports -> jasperreports 4.0.1:
transitive: false
- commons-digester -> commons-digester 1.7:
transitive: false
- com.lowagie -> itext 2.1.7:
transitive: false
- net.sourceforge.jexcelapi -> jxl 2.6.10:
transitive: false
package controllers;
import play.*;
import play.mvc.*;
import java.util.Map;
import java.util.HashMap;
import models.*;
import controllers.*;
public class SomeController extends extends Base {
public static void download_report() {
Map reportParams = new HashMap();
//jrxml and jasper files should be in app/reports
renderBinary(lib.reports.BaseJasperReport.generateReport("SomeReport", reportParams), "some_report.xls");
}
}
Nice now I know how to put gist from github