Example #1
0
//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)
}
Example #2
0
//Start is called by concorde once the DOM is fully loaded.  Most of the
//application intialization code should be put in here.
func (self *todoApp) Start() {

	//Setup an event handler for the primary input field. The called func
	//creates model instance and puts in the list of todos.
	// JQUERY: Any use of jquery is suspect as it allows many non type-safe operations.
	primaryInput.Dom().On(s5.CHANGE, func(event jquery.Event) {
		if !self.createTodo(primaryInput.Dom().Val()) {
			event.PreventDefault()
		}
	})

	//We need to attach the self.numNotDone string to the proper place
	//in the DOM.  Note that we use the lower-level jquery selector
	//(Select().Children())) plus NewTextAttr() we needed the string
	//child of span#todo-count (so we can't use HtmlId) and because
	//that object is directly in the HTML file.
	// JQUERY: Any use of jquery is suspect as it allows many non type-safe operations.
	selector := jquery.NewJQuery(todoCount.TagName() + "#" + todoCount.Id())
	todoCountSelect := selector.Children("strong")
	s5.Equality(s5.NewTextAttr(s5.WrapJQuery(todoCountSelect)), self.numNotDone)

	//We need to attach the self.plural string to the proper place
	//in the dom.
	s5.Equality(pluralSpan.TextAttribute(), self.plural)

	//These two calls attach the inverse of the empty attribute derived
	//from the model collection the display property (turning them on
	//when the list is not empty).  Note that we can't use the simpler
	//"Equality()" because we want to invert the value. The BooleanInverter
	//is a built in constraint function.
	sectionMain.DisplayAttribute().Attach(
		s5.NewBooleanInverter(self.todos.EmptyAttribute()))
	footer.DisplayAttribute().Attach(
		s5.NewBooleanInverter(self.todos.EmptyAttribute()))

	//This connects the display property of the clearCompleted to the boolean
	//that is true if some of the elements are done.  This effectively
	//turns on the button when there are some elements in the list and
	//some of those elements are done.
	s5.Equality(clearCompleted.DisplayAttribute(), self.someDone)

	//This connects the display in the button to the number of done
	//elements.  Note that this wont be visible if there are no
	//done elements.
	s5.Equality(numCompleted.TextAttribute(), self.numDone)

	//This is the event handler for click on the clearCompleted
	//dom element. We just walk the list of objects building a kill list,
	//then we destroy all the items in the kill list
	//JQUERY: Neither of the jquery params are used.
	clearCompleted.Dom().On(s5.CLICK, func(jquery.Event) {
		all := self.todos.All()
		if len(all) == 0 {
			return
		}
		dead := make([]s5.Model, len(all))
		ct := 0
		for _, model := range all {
			if model.(*todo).done.Value() {
				dead[ct] = model
				ct++
			}
		}
		for _, d := range dead {
			self.todos.Remove(d)
		}
	})

	//toggleAll's behavior is to toggle any items that are not already
	//marked done, unless they are all marked done in which they should all
	//be umarked
	//JQUERY: Neither of the jquery params are used.
	toggleAll.Dom().On(s5.CLICK, func(jquery.Event) {
		desired := true
		//Compare the output of the constraints to see if all are done
		if self.todos.LengthAttribute().Value() == self.numDone.Value() {
			desired = false
		}
		for _, m := range self.todos.All() {
			m.(*todo).done.Set(desired)
		}
	})

	//These are discussed below. These are constraints that depend
	//on *all* the values in the list.
	self.dependsOnAll()
}