//newTodo creates a new todo list item for a given string. Note that the //three primary elements are Attributes so the constraint system can operate //on them. The value provided is used to initialize the displayed text. func newTodo(raw string) *todo { result := &todo{ name: s5.NewStringSimple(raw), done: s5.NewBooleanSimple(false), editing: s5.NewBooleanSimple(false), } result.modName = s5.NewModelName(result) return result }
//newApp creates a new instance of the application object, properly //initialized func newApp() *todoApp { result := &todoApp{} //init the list, setting our own object as the joiner (we meet //the interface Joiner) result.todos = s5.NewList(result) //create initial values of attributes result.numNotDone = s5.NewIntegerSimple(0) result.plural = s5.NewStringSimple("") result.someDone = s5.NewBooleanSimple(false) result.numDone = s5.NewIntegerSimple(0) //done create app object return result }
//Add() is the method that is called in response to an element being //added to the collection (self.todos). This is the "magic" turns an //instance of todo into a view. func (self *todoApp) Add(length int, newObj s5.Model) { model := newObj.(*todo) //note: There are two legal things that can be passed to any //note: of the tag creation methods. Sadly, there is no way //note: to typecheck these until runtime (it is checked then). //note: //note: The two legal things are some type of option, such as //note: Class() or ModelId() that affect the resulting tag. //note: One of the common types of "options" is something creates //note: a constraint between a dom "piece" of the tag being //note: being constructed and some value, usually in the model. //note: //note: The other leagl item is another tag, that will be added //note: as a child of the one it is neted in. This lack of type //note: safety has been chosen for convenience of notation. tree := s5.LI( //LI: Pass in a model ID to generate unique id for this tag, //LI: and make easy to remove the whole subtree by id. s5.ModelId(model), //LI: constraint that toggles the completed property s5.CssExistence(completed, model.done), //LI: constraint that toggles the editing property s5.CssExistence(editing, model.editing), s5.DIV( //DIV: just one CSS class to make it look nice s5.Class(view), s5.INPUT( //INPUT: has a CSS class "toggle" to make it look nice s5.Class(toggle), //INPUT: we force the "type" of this to be the constant "checkbox" (possibly overkill) s5.HtmlAttrEqual(s5.TYPE, s5.NewStringSimple("checkbox")), //INPUT: make the checked attr be equal to the model's done s5.PropEqual(s5.CHECKED, model.done), //INPUT: when clicked, it toggles the value on the model s5.Event(s5.CHANGE, func(e jquery.Event) { model.done.Set(!model.done.Value()) }), ), s5.LABEL( //LABEL: We use a constraint to bind the name attribute of the //LABEL: model to the label's displayed text. s5.TextEqual(model.name), //LABEL: Double clicking on the label causes edit mode s5.Event(s5.DBLCLICK, func(jquery.Event) { model.editing.Set(true) //XXX UGH, don't have a handle to the input object in := s5.HtmlIdFromModel("INPUT", model).Dom() in.SetVal(model.name.Value()) in.Focus() in.Select() }), ), s5.BUTTON( //BUTTON: destroy class makes it look nice s5.Class(destroy), //BUTTON: click function that calls remove on the list //BUTTON: element that was used to create this whole structure //JQUERY: Neither of the jquery params are used. s5.Event(s5.CLICK, func(jquery.Event) { //note: we are calling remove on the *collection* which //note: will end up calling the Remove() method of our //note: joiner. If we don't tell the collection that the //note: model was removed, we could end up with a display //note: that doesn't correctly reflect the constraints //note: state (since these constraints would have dependencies //note: one items no longer visible). self.todos.Remove(model) }), ), //BUTTON ), //DIV s5.INPUT( //INPUT: Use a model to make this input easy to find s5.ModelId(model), //INPUT: edit CSS class to make it look nice s5.Class(edit), //INPUT: wire the placeholder to be name of the model... (overkill?) s5.HtmlAttrEqual(s5.PLACEHOLDER, model.name), //INPUT:the spec calls for escape to cancel editing with no change //INPUT:and for return to commit the changes EXCEPT if the //INPUT:user edited out all the text, then we should delete the //INPUT:whole thing //JQUERY: This uses the jquery selector to get the value of the input. //JQUERY: This uses the event object to get the keyboard code. s5.Event(s5.KEYDOWN, func(e jquery.Event) { //note: This type of "event handler" is the glue that //note: connects a user action to something that manipulates //note: the model. Most event handlers do not need to //note: manipulate the view as well because they have constraints //note: that connect the model to the view. switch e.Which { case 13: //This conversion is a no-op other than the go language type. //The e.Target value is ALREADY a jquery object, but it's not //typed correctly for this function to use conveniently. j := s5.WrapJQuery(jquery.NewJQuery(e.Target)) v := strings.Trim(j.Val(), CUTSET) //check for the special case of making a name=="" if v == "" { self.todos.Remove(model) } else { //just reset the model name and it propagates to display model.name.Set(v) } j.Blur() fallthrough case 27: model.editing.Set(false) primaryInput.Dom().Focus() } }), ), //INPUT ).Build() listContainer.Dom().Append(tree) }