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

org.hyperscala.module.ModularPage.scala Maven / Gradle / Ivy

There is a newer version: 0.9.0
Show newest version
package org.hyperscala.module

import org.hyperscala.html._
import org.hyperscala.Page
import org.hyperscala.web.site.Website
import java.util.concurrent.atomic.AtomicBoolean
import org.powerscala.event.Intercept
import org.powerscala.event.processor.{EventToken, TokenProcessor}

/**
 * ModularPage represents all the functionality within a Webpage for dealing with Modules and Interfaces.
 *
 * @author Matt Hicks 
 */
trait ModularPage {
  this: Page =>

  private val modularPageLoaded = new AtomicBoolean(false)
  private var interfaces = List.empty[Interface]

  val modulesLoading = new TokenProcessor("modulesLoading")
  val modulesLoaded = new TokenProcessor("modulesLoaded")

  def module(name: String) = interfaces.find(i => i.name == name)

  def require(interface: Interface): Unit = synchronized {
    val existing = module(interface.name)
    interface match {
      case module: Module => requireModule(module, existing)
      case iwd: InterfaceWithDefault => requireInterfaceWithDefault(iwd, existing)
      case _ => if (existing.isEmpty) addInterface(interface)
    }
  }

  def require(interface: Interface, default: Module): Unit = interface match {
    case module: Module => throw new RuntimeException("Requiring with default must not be a Module.")
    case iwd: InterfaceWithDefault => throw new RuntimeException("Requiring with default must not be a InterfaceWithDefault.")
    case _ => require(InterfaceWithDefault(interface, default))
  }

  private def requireModule(module: Module, existing: Option[Interface]) = existing match {
    case Some(i) => i match {
      case e: Module => e.version.compare(module.version) match {
        case 1 => // Nothing changes, the current is the newer version
        case 0 => // Nothing changes, they are both the same
        case -1 => replaceInterface(e.name, module)
      }
      case _ => replaceInterface(i.name, module)
    }
    case None => addInterface(module)
  }

  private def requireInterfaceWithDefault(iwd: InterfaceWithDefault, existing: Option[Interface]) = existing match {
    case Some(i) => i match {
      case e: Module => // Nothing changes, module supersedes default
      case e: InterfaceWithDefault => e.default.version.compare(iwd.default.version) match {
        case 1 => // Nothing changes, the current default is the newer version
        case 0 => // Nothing changes, they are both the same
        case -1 => replaceInterface(e.name, iwd)
      }
    }
    case None => addInterface(iwd)
  }

  private def replaceInterface(name: String, replacement: Interface, checkPageLoaded: Boolean = true) = {
    if (checkPageLoaded && modularPageLoaded.get()) {
      throw new RuntimeException("Module with name '%s' is already loaded: %s. Cannot replace after page load with: %s!".format(name, module(name).get, replacement))
    }
    replacement match {
      case module: Module => module.dependencies.foreach(require)
      case _ =>
    }
    interfaces = interfaces.map(i => if (i.name == name) replacement else i)
  }

  private def addInterface(interface: Interface) = {
    interface match {
      case module: Module => module.dependencies.foreach(require)
      case _ =>
    }
    interfaces = (interface :: interfaces.reverse).reverse
    if (modularPageLoaded.get()) {
      loadInterface(interface)
    }
  }

  private def loadInterface(interface: Interface) = interface match {
    case module: Module => loadModule(module)
    case iwd: InterfaceWithDefault => {
      replaceInterface(iwd.name, iwd.default, checkPageLoaded = false)    // Replace the interface with the default at load-time
      loadModule(iwd.default)
    }
    case _ => throw new RuntimeException("No implementation defined for interface: %s".format(interface.name))
  }

  /**
   * Makes sure the module is initialized the first use in the website and then loads it.
   */
  private def loadModule(module: Module) = {
    val initialized = Website().application.getOrElse("initializedModules", Set.empty[Module])
    if (!initialized.contains(module)) {
      module.init()
      Website().application("initializedModules") = initialized + module
    }
    debug("Loading module: %s".format(module.getClass.getSimpleName))
    module.load()
  }

  intercept.beforeRender.on {
    case html: tag.HTML => {
      if (modularPageLoaded.compareAndSet(false, true)) {
        modulesLoading.fire(EventToken)
        val headItems = html.head.contents.collect {
          case script: tag.Script => script
          case link: tag.Link => link
          case style: tag.Style => style
        }.toList
        interfaces.foreach(loadInterface)

        // Make sure module head functionality appears before everything else
        headItems.foreach {
          case h => h.removeFromParent()
        }
        headItems.foreach {
          case h => html.head.contents += h
        }
        modulesLoaded.fire(EventToken)
      }
      Intercept.Continue
    }
    case _ => Intercept.Continue
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy