All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hyperscala.ui.widgets.vs.VisualSearch.scala Maven / Gradle / Ivy

The newest version!
package org.hyperscala.ui.widgets.vs

import org.hyperscala.html._
import org.hyperscala.module.Module
import org.hyperscala.web._
import org.hyperscala.jquery.jQuery
import org.hyperscala.jquery.ui.jQueryUI
import org.hyperscala.realtime.Realtime
import org.powerscala.property._
import org.powerscala.Version
import com.outr.net.http.HttpHandler
import com.outr.net.http.response.HttpResponseStatus
import org.hyperscala.selector.Selector
import argonaut.{CodecJson, JsonObject}
import argonaut.Argonaut._
import com.outr.net.http.request.HttpRequest
import scala.Some
import com.outr.net.http.response.HttpResponse
import com.outr.net.http.content.StringContent
import com.outr.net.http.session.Session

/**
 * @author Matt Hicks 
 */
class VisualSearch extends tag.Div {
  private var _facets = List.empty[VisualSearchFacet]

  this.require(VisualSearch)
  identity

  private val modifying = new ThreadLocal[VisualSearchQuery]

  val query = Property[VisualSearchQuery](default = Some(VisualSearchQuery()))

  def +=(facet: VisualSearchFacet) = synchronized {
    _facets = (facet :: _facets.reverse).reverse
  }

  def -=(facet: VisualSearchFacet) = synchronized {
    _facets = _facets.filterNot(f => f == facet)
  }

  def add(facet: VisualSearchFacet) = this += facet
  def remove(facet: VisualSearchFacet) = this -= facet

  def facets = _facets

  def facet(facet: String) = _facets.find(f => f.name.equalsIgnoreCase(facet))

  def queryHasFacet(facet: String) = {
    query().entries.find(e => e.facet.map(f => f.name).getOrElse("").equalsIgnoreCase(facet)).nonEmpty
  }

  def search(query: VisualSearchQuery) = {}

  override protected def initialize() {
    super.initialize()

    connected[Webpage[Session]] {
      case webpage => {
        val instruction = s"createVisualSearch('${webpage.pageId}', '$identity', '${VisualSearch.Path}');"
        Realtime.sendJavaScript(webpage, instruction, selector = Some(Selector.id(id())), onlyRealtime = false)
      }
    }

    query.change.on {
      case evt => {
        if (query() != modifying.get()) {   // Only send change to client if not the same information sent from client
          val content = "\"%s\"".format(query().query)
          connected[Webpage[Session]] {
            case webpage => Realtime.sendJavaScript(webpage, "setVisualSearchQuery('%s', content);".format(id()), Option(content))
          }
        }
        search(query())
      }
    }
  }

  override def receive(event: String, json: JsonObject) = event match {
    case "search" => {
      val searchRequest = json.as[SearchRequest]
      val q = searchRequest.query
      val f = searchRequest.facets.map(m => VisualSearchEntry(facet(m.head._1), m.head._2))
      val searchQuery = VisualSearchQuery(q, f)
      modifying.set(searchQuery)
      try {
        query := searchQuery
      } finally {
        modifying.remove()
      }
    }
    case _ => super.receive(event, json)
  }
}

case class SearchRequest(query: String, facets: List[Map[String, String]])

object SearchRequest {
  implicit def SearchRequestCodecJson: CodecJson[SearchRequest] = casecodec2(SearchRequest.apply, SearchRequest.unapply)("query", "facets")
}

object VisualSearch extends Module {
  val Path = "/visualsearch/search"

  val name = "visualsearch"
  val version = Version(0, 4, 0)

  override val dependencies = List(jQuery.LatestWithDefault, jQueryUI.LatestWithDefault, Realtime)

  val basePath = "/visualsearch-0.4.0/"

  override def init[S <: Session](website: Website[S]) = {
    website.addHandler(new VisualSearchHandler(website), Path)
    website.addClassPath(basePath, "visualsearch/0.4.0/")
  }

  override def load[S <: Session](page: Webpage[S]) = {
    // TODO: extract underscore and backbone as modules
    page.head.contents += new tag.Link(href = "%slib/css/reset.css".format(basePath), rel = "stylesheet")
    page.head.contents += new tag.Link(href = "%slib/css/icons.css".format(basePath), rel = "stylesheet")
    page.head.contents += new tag.Link(href = "%slib/css/workspace.css".format(basePath), rel = "stylesheet")

    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%svendor/underscore-1.4.3.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%svendor/backbone-0.9.10.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/visualsearch.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/views/search_box.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/views/search_facet.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/views/search_input.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/models/search_facets.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/models/search_query.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/utils/backbone_extensions.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/utils/hotkeys.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/utils/jquery_extensions.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/utils/search_parser.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/utils/inflector.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%slib/js/templates/templates.js".format(basePath))
    page.head.contents += new tag.Script(mimeType = "text/javascript", src = "%shyperscala-visualsearch.js".format(basePath))
  }

}

class VisualSearchHandler[S <: Session](website: Website[S]) extends HttpHandler {
  def onReceive(request: HttpRequest, response: HttpResponse) = {
    val pageId = request.url.parameters.first("pageId")
    val fieldId = request.url.parameters.first("fieldId")

    val page = website.pages.byId[Webpage[S]](pageId).getOrElse(throw new NullPointerException(s"Cannot find page by id: $pageId"))
    val visualSearch = page.getById[VisualSearch](fieldId)
    val results = request.url.parameters.first("requestType") match {
      case "facets" => {
        visualSearch.facets.collect {
          case f if f.allowMultiple || !visualSearch.queryHasFacet(f.name) => if (f.category == null) {
            "\"%s\"".format(f.name)
          } else {
            "{\"label\": \"%s\", \"category\": \"%s\"}".format(f.name, f.category)
          }
        }.mkString("[", ", ", "]")
      }
      case "values" => {
        val facetName = request.url.parameters.first("facet")
        val term = request.url.parameters.first("term")
        visualSearch.facet(facetName) match {
          case Some(facet) => {
            val results = facet.search(term, visualSearch)
            val resultString = results.map(r => "{\"value\": \"%s\", \"label\": \"%s\"}".format(r.value, r.label)).mkString("[", ", ", "]")
            "{\"resultType\": \"%s\", \"exactMatch\": %s, \"results\": %s}".format(facet.resultType.name, facet.exactMatch, resultString)
          }
          case None => "{\"results\": []}"
        }
      }
    }
    response.copy(content = StringContent(results), status = HttpResponseStatus.OK)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy