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

net.liftmodules.ng.js.JsonDeltaFuncs.scala Maven / Gradle / Ivy

package net.liftmodules.ng.js

import net.liftweb._
import http.js._
import json._
import JsCmds._
import JE._

/**
 * Contains utility for generating a JsCmd that will convert one JSON object into another.  This utility is meant
 * to be optimal for minor changes to a model object.  Large model changes will not benefit from this utility.
 * In typical cases, we can expect small incremental changes to a model on the server, which this utility will pluck
 * out that diff and send the minimal information to the client to update.
 */
object JsonDeltaFuncs {
  obj =>
  /** Calculate the delta function to convert the first argument into the second argument. */
  def dfn(val1: JValue, val2: JValue): JsVar => JsCmd = (val1, val2) match {
    case (x, y) if x == y => ref => JsCmds.Noop
    case (JObject(xs), JObject(ys)) => dfnFields(xs, ys)
    case (JArray(xs), JArray(ys)) => dfnArrays(xs, ys)
    case (x, y) => ref => SetExp(ref, y)
  }

  /**
   * Calculates the delta function between two arrays.  Currently best cases are appending values or changing
   * values 1-for-1.  Prepending and big changes result in basically the same payload as the entire array reassigned.
   */
  private def dfnArrays(xs: List[JValue], ys: List[JValue]): JsVar => JsCmd = {
    ref =>
      val len = scala.math.max(xs.length, ys.length)
      val xsp = xs.padTo(len, JNull)

      val deltas = for {
        ((x, y), i) <- xsp.zip(ys).zipWithIndex
      } yield {
        dfn(x, y)(JsVar(ref.varName + "[" + i + "]"))
      }

      val pops = if (xs.length > ys.length)
        Some(JsFor(JsRaw("i=0"), JsLt(JsVar("i"), JInt(xs.length - ys.length)), JsRaw("i++"), Call(ref.varName + ".pop")))
      else None

      (deltas ++ pops).reduceLeftOption(_ & _).getOrElse(JsCmds.Noop)
  }

  private def dfnFields(xs: List[JField], ys: List[JField]): JsVar => JsCmd = {
    ref =>
      def toMap(fs: List[JField]) = (fs map {
        case JField(name, value) => name -> value
      }).toMap
      def toVar(k: String) = JsVar(ref.varName + "[\"" + k + "\"]")

      val xm = toMap(xs)
      val ym = toMap(ys)

      val updates = for {
        (k, yv) <- ym
      } yield {
        val xv = xm.get(k).getOrElse(JNull)
        dfn(xv, yv)(toVar(k))
      }

      val voids = for {
        k <- xm.keySet if !ym.contains(k)
      } yield {
        SetExp(toVar(k), JsRaw("void 0"))
      }

      (updates ++ voids).reduceLeftOption(_ & _).getOrElse(JsCmds.Noop)
  }

  implicit def ToJsonDeltaFunc(j: JValue) = new JValueWithDfn(j)

  class JValueWithDfn(j:JValue) {
    /** Calculates the delta function to convert this JSON object into the argument */
    def dfn(other: JValue): JsVar => JsCmd = obj.dfn(j, other)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy