<commit_before>window.cls || (window.cls = {});
cls.CookieManager || (cls.CookieManager = {});
cls.CookieManager["1.0"] || (cls.CookieManager["1.0"] = {});
cls.CookieManager["1.1"] || (cls.CookieManager["1.1"] = {});

cls.CookieManager.StorageDataBase = function()
{
  /**
    * StorageDataBase
    *
    * this.refetch = function()
    * this.remove_item = function(objectref, callback)
    * this.get_items = function()
    * this.create_objectref = function(cookie, runtimes, /fixed_name/)
    * this.get_item_by_objectref = function(objectref)
    * this.write_item = function(cookie_details)
    *
    */
  this.refetch = function()
  {
    this._dict = {};
    for (var rt_id in this._rts) {
      this._rts[rt_id].get_domain_is_pending = true;
      this._request_runtime_details(this._rts[rt_id]);
    };
  };

  this.remove_item = function(objectref, callback)
  {
    var cookie;
    var callback = callback || this.refetch;
    for (var i=0; i < this.item_list.length; i++)
    {
      if(this.item_list[i].objectref === objectref)
      {
        cookie = this.item_list[i];
        var domain = cookie.domain;
        if(!domain)
        {
          // in case the cookies domain is undefined (cookie is retrieved via JS), try using the runtime domain
          domain = this._rts[cookie.runtimes[0]].hostname;
        }
        var path = cookie.path;
        if(!path)
        {
          // in case the cookies path is undefined (cookie is retrieved via JS), try using "/" which is most likely
          path = "/";
        }
        var tag = tagManager.set_callback(this, callback, []);
        services['cookie-manager'].requestRemoveCookie(tag,[domain, path, cookie.name]);
      }
    }
  };

  this.get_items = function()
  {
    return this.item_list;
  }

  this.create_objectref = function(cookie, runtimes, fixed_name)
  {
    return ((fixed_name || (cookie.domain + "/" + cookie.path + "/" + cookie.name + "/")) + (runtimes || "")).replace(/'/g,"");
  };

  this.get_item_by_objectref = function(objectref)
  {
    for (var i=0; i < this.item_list.length; i++) {
      if(this.item_list[i].objectref === objectref)
      {
        return this.item_list[i];
      }
    };
  }

  this.write_item = function(cookie_details)
  {
    // todo: find runtimes where this will probably end up to make the selection restore work
    this._view._restore_selection = [
      this.create_objectref(
        {
          domain: cookie_details.domain,
          name:   cookie_details.name,
          value:  cookie_details.value,
          path:   cookie_details.path
        },
        cookie_details.runtime
      )
    ];

    var add_cookie_script = 'document.cookie="' + cookie_details.name + '=' + encodeURIComponent(cookie_details.value);
    if(cookie_details.expires) // in case of 0 value the "expires" value should not be written, represents "Session" value
    {
      add_cookie_script += '; expires='+ (new Date(cookie_details.expires).toUTCString());
    }
    add_cookie_script += '; path=' + cookie_details.path + '"';
    var script = add_cookie_script;
    var tag = tagManager.set_callback(this, this.refetch, [cookie_details.runtime]);
    services['ecmascript-debugger'].requestEval(tag,[cookie_details.runtime, 0, 0, script]);
  }

  this._on_active_tab = function(msg)
  {
    // cleanup runtimes directory
    for(var item in this._rts)
    {
      // item is a string, rt_id is a number which can now be compared with what's in msg.runtimes_with_dom
      var rt_id = this._rts[item].rt_id;
      if(msg.runtimes_with_dom.indexOf(rt_id) === -1)
      {
        // runtime was not active and is to be removed from this._rts
        delete this._rts[rt_id];
        // loop over existing cookies to remove the rt_id from the runtimes of each
        for(var domain_and_path in this._dict)
        {
          if(this._dict[domain_and_path].runtimes && (this._dict[domain_and_path].runtimes.indexOf(rt_id) !== -1))
          {
            var index = this._dict[domain_and_path].runtimes.indexOf(rt_id);
            this._dict[domain_and_path].runtimes.splice(index,1);
            // if no runtimes are left, delete from _dict
            if(this._dict[domain_and_path].runtimes.length < 1)
            {
              delete this._dict[domain_and_path];
            }
          }
        }
      }
    }

    for (var i=0; i < msg.runtimes_with_dom.length; i++)
    {
      var rt_id = msg.runtimes_with_dom[i];
      if(!this._rts[rt_id])
      {
        this._rts[rt_id]={rt_id: rt_id, get_domain_is_pending: true};
      }
      this._request_runtime_details(this._rts[rt_id]);
    };
  };

  this._update = function()
  {
    if(this._hold_redraw_mem.active)
    {
      this._hold_redraw_mem.callback = this.update.bind(this);
    }
    else
    {
      this.item_list = this._flatten_data(this._dict, this._rts);
      this._view.update();
    }
  }

  this._is_min_service_version = function(compare_version)
  {
    var compare_version = compare_version.split(".").map(Number);
    var service_version = this.service_version.split(".").map(Number);
    for (var i=0; i < compare_version.length; i++) {
      if((service_version[i] || 0) < compare_version[i])
      {
        return false;
      }
    };
    return true;
  };

  this._flatten_data = function(cookies, runtimes)
  {
    var flattened_cookies = [];
    for (var id in cookies)
    {
      var domaincookies = cookies[id];
      if (domaincookies.items)
      {
        for (var i=0; i < domaincookies.items.length ;i++)
        {
          var current_cookie = domaincookies.items[i];
          var flattened_cookie = {
            objectref:    this.create_objectref(current_cookie, domaincookies.runtimes),
            runtimes:     domaincookies.runtimes,
            /**
             * Decide if the cookie can be edited.
             * The cookie.domain and .isHTTPOnly conditions applies only when the "add cookie"
             * interface is not used, which allows specifying the domain when creating cookies
             * cookie_service 1.0.2 fixes CORE-35055 -> correct paths, allows for editing
            */
            is_editable:  this._is_min_service_version("1.1") || (
                            !current_cookie.isHTTPOnly &&
                            (!current_cookie.path || this._is_min_service_version("1.0.2")) &&
                            current_cookie.domain === runtimes[domaincookies.runtimes[0]].hostname
                          ),
            /**
             * Decide if the cookie can be removed.
             * Cookie retrieved via JS can't reliably be removed because domain (and path) are unknown.
             * Also while path info is mostly incorrect when present (CORE-35055), cookie with path
             * won't be removable for service_versions < 1.0.2.
            */
            is_removable: (
                            current_cookie.domain !== undefined &&
                            current_cookie.path !== undefined &&
                            (!current_cookie.path || this._is_min_service_version("1.0.2"))
                          )
          };
          for (var key in current_cookie) {
            flattened_cookie[key] = current_cookie[key];
          };
          flattened_cookies.push(flattened_cookie);
        };
      }
      else
      {
        // There are no cookies for this domain/path. The group needs to be shown anyhow.
        flattened_cookies.push({
          runtimes:                domaincookies.runtimes,
          objectref:               this.create_objectref(this, domaincookies.runtimes, "domain_path_placeholder"),
          is_runtimes_placeholder: true
        });
      }
    }
    return flattened_cookies;
  };

  this._request_runtime_details = function(rt_object)
  {
    var script = "return JSON.stringify({hostname: location.hostname || '', pathname: location.pathname || ''})";
    var tag = tagManager.set_callback(this, this._handle_get_domain,[rt_object.rt_id]);
    services['ecmascript-debugger'].requestEval(tag,[rt_object.rt_id, 0, 0, script]);
  };

  this._handle_get_domain = function(status, message, rt_id)
  {
    const STATUS = 0;
    const DATA = 2;
    if(status === 0 && message[STATUS] == "completed")
    {
      var parsed_data = JSON.parse(message[DATA]);
      var hostname = parsed_data.hostname;
      var pathname = parsed_data.pathname;
      this._rts[rt_id].get_domain_is_pending = false;
      this._rts[rt_id].hostname = hostname;
      this._rts[rt_id].pathname = pathname;

      (function(context)
      {
        for (var key in context._rts)
        {
          if(context._rts[key]["get_domain_is_pending"] !== false)
          {
            return;
          }
        };
        context._request_data.call(context,context._rts);
      })(this);
    }
  };

  this._request_data = function(runtime_list)
  {
    // go over runtimes and ask for cookies once per domain
    for (var str_rt_id in runtime_list)
    {
      var runtime = runtime_list[str_rt_id];
      var rt_domain = runtime.hostname;
      var rt_pathname = runtime.pathname;
      if(rt_domain)
      {
        if(!this._dict[rt_domain+rt_pathname])
        {
          this._dict[rt_domain+rt_pathname] = {
            runtimes: [runtime.rt_id]
          }
        }
        else
        {
          if(this._dict[rt_domain+rt_pathname].runtimes.indexOf(runtime.rt_id) === -1)
          {
            this._dict[rt_domain+rt_pathname].runtimes.push(runtime.rt_id);
          }
        }

        // avoid repeating cookie requests for domains being in more than one runtime
        if(!this._dict[rt_domain+rt_pathname].get_cookies_is_pending)
        {
          this._dict[rt_domain+rt_pathname].get_cookies_is_pending = true;
          var tag = tagManager.set_callback(this, this._handle_cookies,[rt_domain,rt_pathname]);
          services['cookie-manager'].requestGetCookie(tag,[rt_domain,rt_pathname]);
        }
      }
      else
      {
        // if runtime has no location.hostname, only update view. occurs on opera:* pages for example.
        this._update();
      }
    }
  };

  this._handle_cookies = function(status, message, domain, path)
  {
    if(status === 0)
    {
      const COOKIE = 0;
      this._dict[domain+path].get_cookies_is_pending=false;
      if(message.length > 0)
      {
        var cookies = message[COOKIE];
        this._dict[domain+path].items=[];
        for (var i=0; i < cookies.length; i++) {
          var cookie_info = cookies[i];
          this._dict[domain+path].items.push({
            domain:     cookie_info[0],
            path:       cookie_info[1],
            name:       cookie_info[2],
            value:      decodeURIComponent(cookie_info[3]),
            expires:    cookie_info[4],
            isSecure:   cookie_info[5],
            isHTTPOnly: cookie_info[6]
          });
        };
      }
      else
      {
        // In case no cookies come back, check via JS (workaround for CORE-35055)
        // Find runtime that has the appropriate domain and path
        for(var id in this._rts)
        {
          var runtime = this._rts[id];
          if(runtime.hostname === domain && runtime.pathname === path)
          {
            var script = "return document.cookie";
            var tag = tagManager.set_callback(this, this._handle_js_retrieved_cookies, [domain, path]);
            services['ecmascript-debugger'].requestEval(tag,[parseInt(id), 0, 0, script]);
            break;
          }
        }
      }
    }
    this._update();
  };

  this._handle_js_retrieved_cookies = function(status, message, domain, path)
  {
    const STATUS = 0;
    const DATA = 2;
    if(status === 0 && message[STATUS] == "completed")
    {
      var cookie_string = message[DATA];
      if(cookie_string && cookie_string.length > 0)
      {
        this._dict[domain+path].items=[];
        var cookies = cookie_string.split(';');
        for (var i=0; i < cookies.length; i++) {
          var cookie_info = cookies[i];
          var pos = cookie_info.indexOf('=', 0);
          this._dict[domain+path].items.push({
            name:  cookie_info.slice(0, pos),
            value: decodeURIComponent(cookie_info.slice(pos+1))
          });
        };
        this._update();
      }
    }
  };

  this._init = function(service_version, view)
  {
    this.service_version = service_version || 0;
    this._view = view;
    this._dict = {};
    this.item_list = [];
    this._rts = {};
    window.messages.addListener('active-tab', this._on_active_tab.bind(this));
    this._hold_redraw_mem = {};
  };
};

cls.CookieManager["1.0"].CookieManagerData = function(service_version, view)
{
  this._init(service_version, view);
}
cls.CookieManager["1.0"].CookieManagerData.prototype = new cls.CookieManager.StorageDataBase();

cls.CookieManager["1.1"].CookieManagerData = function(service_version, view)
{
  this.write_item = function(cookie_details)
  {
    this._view._restore_selection = [
      this.create_objectref(
        {
          domain: cookie_details.domain,
          name:   cookie_details.name,
          value:  cookie_details.value,
          path:   cookie_details.path
        },
        cookie_details.runtime
      )
    ];
    var tag = tagManager.set_callback(this, this.refetch);
    var cookie_detail_arr = [cookie_details.domain, cookie_details.name, cookie_details.path, cookie_details.value, cookie_details.expires / 1000, cookie_details.is_secure, cookie_details.is_http_only];
    services['cookie-manager'].requestAddCookie(tag, cookie_detail_arr);
  }
  this._init(service_version, view);
}
cls.CookieManager["1.1"].CookieManagerData.prototype = new cls.CookieManager.StorageDataBase();
<commit_msg>Slight simplification<commit_after>window.cls || (window.cls = {});
cls.CookieManager || (cls.CookieManager = {});
cls.CookieManager["1.0"] || (cls.CookieManager["1.0"] = {});
cls.CookieManager["1.1"] || (cls.CookieManager["1.1"] = {});

cls.CookieManager.StorageDataBase = function()
{
  /**
    * StorageDataBase
    *
    * this.refetch = function()
    * this.remove_item = function(objectref, callback)
    * this.get_items = function()
    * this.create_objectref = function(cookie, runtimes, /fixed_name/)
    * this.get_item_by_objectref = function(objectref)
    * this.write_item = function(cookie_details)
    *
    */
  this.refetch = function()
  {
    this._dict = {};
    for (var rt_id in this._rts) {
      this._rts[rt_id].get_domain_is_pending = true;
      this._request_runtime_details(this._rts[rt_id]);
    };
  };

  this.remove_item = function(objectref, callback)
  {
    var cookie;
    var callback = callback || this.refetch;
    for (var i=0; i < this.item_list.length; i++)
    {
      if(this.item_list[i].objectref === objectref)
      {
        cookie = this.item_list[i];
        var domain = cookie.domain;
        if(!domain)
        {
          // in case the cookies domain is undefined (cookie is retrieved via JS), try using the runtime domain
          domain = this._rts[cookie.runtimes[0]].hostname;
        }
        var path = cookie.path;
        if(!path)
        {
          // in case the cookies path is undefined (cookie is retrieved via JS), try using "/" which is most likely
          path = "/";
        }
        var tag = tagManager.set_callback(this, callback, []);
        services['cookie-manager'].requestRemoveCookie(tag,[domain, path, cookie.name]);
      }
    }
  };

  this.get_items = function()
  {
    return this.item_list;
  }

  this.create_objectref = function(cookie, runtimes, fixed_name)
  {
    return ((fixed_name || (cookie.domain + "/" + cookie.path + "/" + cookie.name + "/")) + (runtimes || "")).replace(/'/g,"");
  };

  this.get_item_by_objectref = function(objectref)
  {
    for (var i=0; i < this.item_list.length; i++) {
      if(this.item_list[i].objectref === objectref)
      {
        return this.item_list[i];
      }
    };
  }

  this.write_item = function(cookie_details)
  {
    // todo: find runtimes where this will probably end up to make the selection restore work
    this._view._restore_selection = [
      this.create_objectref(
        {
          domain: cookie_details.domain,
          name:   cookie_details.name,
          value:  cookie_details.value,
          path:   cookie_details.path
        },
        cookie_details.runtime
      )
    ];

    var add_cookie_script = 'document.cookie="' + cookie_details.name + '=' + encodeURIComponent(cookie_details.value);
    if(cookie_details.expires) // in case of 0 value the "expires" value should not be written, represents "Session" value
    {
      add_cookie_script += '; expires='+ (new Date(cookie_details.expires).toUTCString());
    }
    add_cookie_script += '; path=' + cookie_details.path + '"';
    var script = add_cookie_script;
    var tag = tagManager.set_callback(this, this.refetch, [cookie_details.runtime]);
    services['ecmascript-debugger'].requestEval(tag,[cookie_details.runtime, 0, 0, script]);
  }

  this._on_active_tab = function(msg)
  {
    // cleanup runtimes directory
    for(var item in this._rts)
    {
      // item is a string, rt_id is a number which can now be compared with what's in msg.runtimes_with_dom
      var rt_id = this._rts[item].rt_id;
      if(msg.runtimes_with_dom.indexOf(rt_id) === -1)
      {
        // runtime was not active and is to be removed from this._rts
        delete this._rts[rt_id];
        // loop over existing cookies to remove the rt_id from the runtimes of each
        for(var domain_and_path in this._dict)
        {
          if(this._dict[domain_and_path].runtimes && (this._dict[domain_and_path].runtimes.indexOf(rt_id) !== -1))
          {
            var index = this._dict[domain_and_path].runtimes.indexOf(rt_id);
            this._dict[domain_and_path].runtimes.splice(index,1);
            // if no runtimes are left, delete from _dict
            if(this._dict[domain_and_path].runtimes.length < 1)
            {
              delete this._dict[domain_and_path];
            }
          }
        }
      }
    }

    for (var i=0; i < msg.runtimes_with_dom.length; i++)
    {
      var rt_id = msg.runtimes_with_dom[i];
      if(!this._rts[rt_id])
      {
        this._rts[rt_id]={rt_id: rt_id, get_domain_is_pending: true};
      }
      this._request_runtime_details(this._rts[rt_id]);
    };
  };

  this._update = function()
  {
    if(this._hold_redraw_mem.active)
    {
      this._hold_redraw_mem.callback = this.update.bind(this);
    }
    else
    {
      this.item_list = this._flatten_data(this._dict, this._rts);
      this._view.update();
    }
  }

  this._is_min_service_version = function(compare_version)
  {
    var compare_version = compare_version.split(".").map(Number);
    var service_version = this.service_version.split(".").map(Number);
    for (var i=0; i < compare_version.length; i++) {
      if((service_version[i] || 0) < compare_version[i])
      {
        return false;
      }
    };
    return true;
  };

  this._flatten_data = function(cookies, runtimes)
  {
    var flattened_cookies = [];
    for (var id in cookies)
    {
      var domaincookies = cookies[id];
      if (domaincookies.items)
      {
        for (var i=0; i < domaincookies.items.length ;i++)
        {
          var current_cookie = domaincookies.items[i];
          var flattened_cookie = {
            objectref:    this.create_objectref(current_cookie, domaincookies.runtimes),
            runtimes:     domaincookies.runtimes,
            /**
             * Decide if the cookie can be edited.
             * The cookie.domain and .isHTTPOnly conditions applies only when the "add cookie"
             * interface is not used, which allows specifying the domain when creating cookies
             * cookie_service 1.0.2 fixes CORE-35055 -> correct paths, allows for editing
            */
            is_editable:  this._is_min_service_version("1.1") || (
                            !current_cookie.isHTTPOnly &&
                            (!current_cookie.path || this._is_min_service_version("1.0.2")) &&
                            current_cookie.domain === runtimes[domaincookies.runtimes[0]].hostname
                          ),
            /**
             * Decide if the cookie can be removed.
             * Cookie retrieved via JS can't reliably be removed because domain (and path) are unknown.
             * Also while path info is mostly incorrect when present (CORE-35055), cookie with path
             * won't be removable for service_versions < 1.0.2.
            */
            is_removable: (
                            current_cookie.domain !== undefined &&
                            current_cookie.path !== undefined &&
                            (!current_cookie.path || this._is_min_service_version("1.0.2"))
                          )
          };
          for (var key in current_cookie) {
            flattened_cookie[key] = current_cookie[key];
          };
          flattened_cookies.push(flattened_cookie);
        };
      }
      else
      {
        // There are no cookies for this domain/path. The group needs to be shown anyhow.
        flattened_cookies.push({
          runtimes:                domaincookies.runtimes,
          objectref:               this.create_objectref(this, domaincookies.runtimes, "domain_path_placeholder"),
          is_runtimes_placeholder: true
        });
      }
    }
    return flattened_cookies;
  };

  this._request_runtime_details = function(rt_object)
  {
    var script = "return JSON.stringify({hostname: location.hostname || '', pathname: location.pathname || ''})";
    var tag = tagManager.set_callback(this, this._handle_runtime_details,[rt_object.rt_id]);
    services['ecmascript-debugger'].requestEval(tag,[rt_object.rt_id, 0, 0, script]);
  };

  this._handle_runtime_details = function(status, message, rt_id)
  {
    const STATUS = 0;
    const DATA = 2;
    if(status === 0 && message[STATUS] == "completed")
    {
      var parsed_data = JSON.parse(message[DATA]);
      var hostname = parsed_data.hostname;
      var pathname = parsed_data.pathname;
      this._rts[rt_id].get_domain_is_pending = false;
      this._rts[rt_id].hostname = hostname;
      this._rts[rt_id].pathname = pathname;

      // wait for domain info for runtime details of all runtimes
      var do_request_data = true;
      for (var key in this._rts)
      {
        if(this._rts[key].get_domain_is_pending === true)
        {
          do_request_data = false;
        }
      };
      if(do_request_data)
      {
        this._request_data(this._rts);
      }
    }
  };

  this._request_data = function(runtime_list)
  {
    // go over runtimes and ask for cookies once per domain
    for (var str_rt_id in runtime_list)
    {
      var runtime = runtime_list[str_rt_id];
      var rt_domain = runtime.hostname;
      var rt_pathname = runtime.pathname;
      if(rt_domain)
      {
        if(!this._dict[rt_domain+rt_pathname])
        {
          this._dict[rt_domain+rt_pathname] = {
            runtimes: [runtime.rt_id]
          }
        }
        else
        {
          if(this._dict[rt_domain+rt_pathname].runtimes.indexOf(runtime.rt_id) === -1)
          {
            this._dict[rt_domain+rt_pathname].runtimes.push(runtime.rt_id);
          }
        }

        // avoid repeating cookie requests for domains being in more than one runtime
        if(!this._dict[rt_domain+rt_pathname].get_cookies_is_pending)
        {
          this._dict[rt_domain+rt_pathname].get_cookies_is_pending = true;
          var tag = tagManager.set_callback(this, this._handle_cookies,[rt_domain,rt_pathname]);
          services['cookie-manager'].requestGetCookie(tag,[rt_domain,rt_pathname]);
        }
      }
      else
      {
        // if runtime has no location.hostname, only update view. occurs on opera:* pages for example.
        this._update();
      }
    }
  };

  this._handle_cookies = function(status, message, domain, path)
  {
    if(status === 0)
    {
      const COOKIE = 0;
      this._dict[domain+path].get_cookies_is_pending=false;
      if(message.length > 0)
      {
        var cookies = message[COOKIE];
        this._dict[domain+path].items=[];
        for (var i=0; i < cookies.length; i++) {
          var cookie_info = cookies[i];
          this._dict[domain+path].items.push({
            domain:     cookie_info[0],
            path:       cookie_info[1],
            name:       cookie_info[2],
            value:      decodeURIComponent(cookie_info[3]),
            expires:    cookie_info[4],
            isSecure:   cookie_info[5],
            isHTTPOnly: cookie_info[6]
          });
        };
      }
      else
      {
        // In case no cookies come back, check via JS (workaround for CORE-35055)
        // Find runtime that has the appropriate domain and path
        for(var id in this._rts)
        {
          var runtime = this._rts[id];
          if(runtime.hostname === domain && runtime.pathname === path)
          {
            var script = "return document.cookie";
            var tag = tagManager.set_callback(this, this._handle_js_retrieved_cookies, [domain, path]);
            services['ecmascript-debugger'].requestEval(tag,[parseInt(id), 0, 0, script]);
            break;
          }
        }
      }
    }
    this._update();
  };

  this._handle_js_retrieved_cookies = function(status, message, domain, path)
  {
    const STATUS = 0;
    const DATA = 2;
    if(status === 0 && message[STATUS] == "completed")
    {
      var cookie_string = message[DATA];
      if(cookie_string && cookie_string.length > 0)
      {
        this._dict[domain+path].items=[];
        var cookies = cookie_string.split(';');
        for (var i=0; i < cookies.length; i++) {
          var cookie_info = cookies[i];
          var pos = cookie_info.indexOf('=', 0);
          this._dict[domain+path].items.push({
            name:  cookie_info.slice(0, pos),
            value: decodeURIComponent(cookie_info.slice(pos+1))
          });
        };
        this._update();
      }
    }
  };

  this._init = function(service_version, view)
  {
    this.service_version = service_version || 0;
    this._view = view;
    this._dict = {};
    this.item_list = [];
    this._rts = {};
    window.messages.addListener('active-tab', this._on_active_tab.bind(this));
    this._hold_redraw_mem = {};
  };
};

cls.CookieManager["1.0"].CookieManagerData = function(service_version, view)
{
  this._init(service_version, view);
}
cls.CookieManager["1.0"].CookieManagerData.prototype = new cls.CookieManager.StorageDataBase();

cls.CookieManager["1.1"].CookieManagerData = function(service_version, view)
{
  this.write_item = function(cookie_details)
  {
    this._view._restore_selection = [
      this.create_objectref(
        {
          domain: cookie_details.domain,
          name:   cookie_details.name,
          value:  cookie_details.value,
          path:   cookie_details.path
        },
        cookie_details.runtime
      )
    ];
    var tag = tagManager.set_callback(this, this.refetch);
    var cookie_detail_arr = [cookie_details.domain, cookie_details.name, cookie_details.path, cookie_details.value, cookie_details.expires / 1000, cookie_details.is_secure, cookie_details.is_http_only];
    services['cookie-manager'].requestAddCookie(tag, cookie_detail_arr);
  }
  this._init(service_version, view);
}
cls.CookieManager["1.1"].CookieManagerData.prototype = new cls.CookieManager.StorageDataBase();
<|endoftext|>


<commit_before>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Front of the HeaterCoolerNode.  It is independent from the HeaterCoolerBack so that one can easily layer objects
 * inside of the HeaterCoolerNode.  The HeaterCoolerFront contains the heater body, labels, and control slider.
 *
 * @author Siddhartha Chinthapally (Actual Concepts) on 20-11-2014.
 * @author Jesse Greenberg
 *
 */

define( require => {
  'use strict';

  // modules
  const BooleanProperty = require( 'AXON/BooleanProperty' );
  const Color = require( 'SCENERY/util/Color' );
  const Dimension2 = require( 'DOT/Dimension2' );
  const HeaterCoolerBack = require( 'SCENERY_PHET/HeaterCoolerBack' );
  const LinearGradient = require( 'SCENERY/util/LinearGradient' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const NodeIO = require( 'SCENERY/nodes/NodeIO' );
  const Path = require( 'SCENERY/nodes/Path' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Range = require( 'DOT/Range' );
  const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  const Shape = require( 'KITE/Shape' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Text = require( 'SCENERY/nodes/Text' );
  const VSlider = require( 'SUN/VSlider' );

  // strings
  const coolString = require( 'string!SCENERY_PHET/cool' );
  const heatString = require( 'string!SCENERY_PHET/heat' );

  // constants
  const DEFAULT_WIDTH = 120; // in screen coords, much of the rest of the size of the stove derives from this value
  const DEFAULT_BASE_COLOR = 'rgb( 159, 182, 205 )';

  class HeaterCoolerFront extends Node {

    /**
     * @param {NumberProperty} heatCoolAmountProperty // +1 for max heating, -1 for max cooling
     * @param {Object} [options]
     * @constructor
     */
    constructor( heatCoolAmountProperty, options ) {
      super();

      options = merge( {
        baseColor: DEFAULT_BASE_COLOR, // {Color|string} Base color used for the stove body.
        width: 120, // In screen coords, much of the rest of the size of the stove derives from this value.
        heatEnabled: true, // Allows slider to reach positive values (corresponding to heating)
        coolEnabled: true, // Allows slider to reach negative values (corresponding to cooling)
        snapToZero: true, // see doc at this.snapToZeroProperty

        // the percentage of the slider's minimum and maximum range at which the slider should snap to zero when
        // released. Note that it's only used when this.snapToZeroProperty is false and when both heating and cooling
        // are enabled. Default value empirically determined, see https://github.com/phetsims/scenery-phet/issues/568
        snapToZeroThreshold: 0.1,

        // slider label options
        heatString: heatString, // {string} label for +1 end of slider
        coolString: coolString, // {string} label for -1 end of slider
        labelFont: new PhetFont( 14 ), // {Font}
        labelMaxWidth: 35, // {number} maxWidth of the Heat and Cool labels, determined empirically

        // slider options
        thumbSize: new Dimension2( 45, 22 ), // {Dimension2}
        thumbTouchAreaXDilation: 11, // {number}
        thumbTouchAreaYDilation: 11, // {number}
        thumbMouseAreaXDilation: 0, // {number}
        thumbMouseAreaYDilation: 0, // {number}
        thumbFill: '#71edff', // {Color|string|null}
        thumbFillHighlighted: '#bff7ff', // {Color|string|null}

        // {null|HeaterCoolerBack} links the NodeIO Properties of the provided HeaterCoolerBack to this HeaterCoolerFront
        heaterCoolerBack: null,

        sliderOptions: {
          trackSize: new Dimension2( 10, DEFAULT_WIDTH / 2 ), // height of the track depends on the width
          trackFillEnabled: new LinearGradient( 0, 0, DEFAULT_WIDTH / 2, 0 )
            .addColorStop( 0, '#0A00F0' )
            .addColorStop( 1, '#EF000F' ),
          thumbLineWidth: 1.4,
          thumbCenterLineStroke: 'black',
          majorTickLength: 15,
          minorTickLength: 12
        },

        // phet-io
        tandem: Tandem.REQUIRED,
        phetioType: NodeIO
      }, options );

      assert && assert( options.heatEnabled || options.coolEnabled, 'Either heat or cool must be enabled.' );
      assert && assert( options.snapToZeroThreshold >= 0 && options.snapToZeroThreshold <= 1,
        'options.snapToZeroThreshold must be between 0 and 1: ' + options.snapToZeroThreshold );

      // Dimensions for the rest of the stove, dependent on the specified stove width.  Empirically determined, and could
      // be made into options if needed.
      const height = DEFAULT_WIDTH * 0.75;
      const burnerOpeningHeight = DEFAULT_WIDTH * HeaterCoolerBack.OPENING_HEIGHT_SCALE;
      const bottomWidth = DEFAULT_WIDTH * 0.80;

      // Create the body of the stove.
      const stoveBodyShape = new Shape()
        .ellipticalArc( DEFAULT_WIDTH / 2, burnerOpeningHeight / 4, DEFAULT_WIDTH / 2, burnerOpeningHeight / 2, 0, 0, Math.PI, false )
        .lineTo( ( DEFAULT_WIDTH - bottomWidth ) / 2, height + burnerOpeningHeight / 2 )
        .ellipticalArc( DEFAULT_WIDTH / 2, height + burnerOpeningHeight / 4, bottomWidth / 2, burnerOpeningHeight,
          0, Math.PI, 0, true ).lineTo( DEFAULT_WIDTH, burnerOpeningHeight / 2 );

      const stoveBody = new Path( stoveBodyShape, {
        stroke: 'black',
        fill: new LinearGradient( 0, 0, DEFAULT_WIDTH, 0 )
          .addColorStop( 0, Color.toColor( options.baseColor ).brighterColor( 0.5 ) )
          .addColorStop( 1, Color.toColor( options.baseColor ).darkerColor( 0.5 ) )
      } );

      // @private {BooleanProperty}
      this.snapToZeroProperty = new BooleanProperty( options.snapToZero, {
        tandem: options.tandem.createTandem( 'snapToZeroProperty' ),
        phetioDocumentation: 'whether the slider will snap to the off position when released',
        phetioFeatured: true
      } );

      const setSliderToZero = () => {
        heatCoolAmountProperty.set( 0 );
      };
      const sliderRange = new Range( options.coolEnabled ? -1 : 0, options.heatEnabled ? 1 : 0 );

      /**
       * determines if the slider is close enough to zero to snap to zero (even when snapToZeroProperty is false). It's
       * only applicable when both heating and cooling are enabled because that is the only configuration where it was
       * difficult for a user to set the slider to 0. This feature was requested by designers, see https://github.com/phetsims/scenery-phet/issues/568.
       * @returns {boolean}
       */
      const sliderIsCloseToZero = () => {
        return options.coolEnabled && options.heatEnabled && (
          heatCoolAmountProperty.value < 0 && heatCoolAmountProperty.value / sliderRange.min < options.snapToZeroThreshold ||
          heatCoolAmountProperty.value > 0 && heatCoolAmountProperty.value / sliderRange.max < options.snapToZeroThreshold );
      };

      const sliderOptions = merge( {
        thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
        thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
        thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
        thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
        thumbFill: options.thumbFill,
        thumbSize: options.thumbSize,
        thumbFillHighlighted: options.thumbFillHighlighted,
        endDrag: () => {
          if ( this.snapToZeroProperty.value || sliderIsCloseToZero() ) {
            setSliderToZero();
          }
        },
        centerY: stoveBody.centerY,
        right: stoveBody.right - DEFAULT_WIDTH / 8,
        tandem: options.tandem.createTandem( 'slider' )
      }, options.sliderOptions );

      // @public (read-only), please use judiciously, see https://github.com/phetsims/scenery-phet/issues/442
      this.slider = new VSlider(
        heatCoolAmountProperty,
        sliderRange,
        sliderOptions
      );

      // Create the tick labels.
      const labelOptions = {
        font: options.labelFont,
        maxWidth: options.labelMaxWidth
      };
      if ( options.heatEnabled ) { this.slider.addMajorTick( 1, new Text( options.heatString, labelOptions ) ); }
      this.slider.addMinorTick( 0 );
      if ( options.coolEnabled ) { this.slider.addMajorTick( -1, new Text( options.coolString, labelOptions ) ); }

      this.addChild( stoveBody );
      this.addChild( this.slider );

      this.mutate( options );

      // update the back component if provided
      if ( options.heaterCoolerBack ) {
        this.on( 'opacity', () => {
          options.heaterCoolerBack.opacity = this.opacity;
        } );
        this.on( 'pickability', () => {
          options.heaterCoolerBack.pickable = this.pickable;
        } );
        this.on( 'visibility', () => {
          options.heaterCoolerBack.visible = this.visible;
        } );
      }

      // return the slider to its origin if snapToZero is changed to true
      this.snapToZeroProperty.link( snapToZero => {
        snapToZero && setSliderToZero();
      } );
    }
  }

  return sceneryPhet.register( 'HeaterCoolerFront', HeaterCoolerFront );
} );
<commit_msg>Add comment for snapToZeroThreshold limit cases, see https://github.com/phetsims/scenery-phet/issues/568<commit_after>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Front of the HeaterCoolerNode.  It is independent from the HeaterCoolerBack so that one can easily layer objects
 * inside of the HeaterCoolerNode.  The HeaterCoolerFront contains the heater body, labels, and control slider.
 *
 * @author Siddhartha Chinthapally (Actual Concepts) on 20-11-2014.
 * @author Jesse Greenberg
 *
 */

define( require => {
  'use strict';

  // modules
  const BooleanProperty = require( 'AXON/BooleanProperty' );
  const Color = require( 'SCENERY/util/Color' );
  const Dimension2 = require( 'DOT/Dimension2' );
  const HeaterCoolerBack = require( 'SCENERY_PHET/HeaterCoolerBack' );
  const LinearGradient = require( 'SCENERY/util/LinearGradient' );
  const merge = require( 'PHET_CORE/merge' );
  const Node = require( 'SCENERY/nodes/Node' );
  const NodeIO = require( 'SCENERY/nodes/NodeIO' );
  const Path = require( 'SCENERY/nodes/Path' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Range = require( 'DOT/Range' );
  const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  const Shape = require( 'KITE/Shape' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Text = require( 'SCENERY/nodes/Text' );
  const VSlider = require( 'SUN/VSlider' );

  // strings
  const coolString = require( 'string!SCENERY_PHET/cool' );
  const heatString = require( 'string!SCENERY_PHET/heat' );

  // constants
  const DEFAULT_WIDTH = 120; // in screen coords, much of the rest of the size of the stove derives from this value
  const DEFAULT_BASE_COLOR = 'rgb( 159, 182, 205 )';

  class HeaterCoolerFront extends Node {

    /**
     * @param {NumberProperty} heatCoolAmountProperty // +1 for max heating, -1 for max cooling
     * @param {Object} [options]
     * @constructor
     */
    constructor( heatCoolAmountProperty, options ) {
      super();

      options = merge( {
        baseColor: DEFAULT_BASE_COLOR, // {Color|string} Base color used for the stove body.
        width: 120, // In screen coords, much of the rest of the size of the stove derives from this value.
        heatEnabled: true, // Allows slider to reach positive values (corresponding to heating)
        coolEnabled: true, // Allows slider to reach negative values (corresponding to cooling)
        snapToZero: true, // see doc at this.snapToZeroProperty

        // the percentage of the slider's minimum and maximum range at which the slider should snap to zero when
        // released. Note that it's only used when this.snapToZeroProperty is false and when both heating and cooling
        // are enabled. A value of 1 is the same as snapToZero: true, and a value of 0 removes snapping entirely.
        // Default value empirically determined, see https://github.com/phetsims/scenery-phet/issues/568
        snapToZeroThreshold: 0.1,

        // slider label options
        heatString: heatString, // {string} label for +1 end of slider
        coolString: coolString, // {string} label for -1 end of slider
        labelFont: new PhetFont( 14 ), // {Font}
        labelMaxWidth: 35, // {number} maxWidth of the Heat and Cool labels, determined empirically

        // slider options
        thumbSize: new Dimension2( 45, 22 ), // {Dimension2}
        thumbTouchAreaXDilation: 11, // {number}
        thumbTouchAreaYDilation: 11, // {number}
        thumbMouseAreaXDilation: 0, // {number}
        thumbMouseAreaYDilation: 0, // {number}
        thumbFill: '#71edff', // {Color|string|null}
        thumbFillHighlighted: '#bff7ff', // {Color|string|null}

        // {null|HeaterCoolerBack} links the NodeIO Properties of the provided HeaterCoolerBack to this HeaterCoolerFront
        heaterCoolerBack: null,

        sliderOptions: {
          trackSize: new Dimension2( 10, DEFAULT_WIDTH / 2 ), // height of the track depends on the width
          trackFillEnabled: new LinearGradient( 0, 0, DEFAULT_WIDTH / 2, 0 )
            .addColorStop( 0, '#0A00F0' )
            .addColorStop( 1, '#EF000F' ),
          thumbLineWidth: 1.4,
          thumbCenterLineStroke: 'black',
          majorTickLength: 15,
          minorTickLength: 12
        },

        // phet-io
        tandem: Tandem.REQUIRED,
        phetioType: NodeIO
      }, options );

      assert && assert( options.heatEnabled || options.coolEnabled, 'Either heat or cool must be enabled.' );
      assert && assert( options.snapToZeroThreshold >= 0 && options.snapToZeroThreshold <= 1,
        'options.snapToZeroThreshold must be between 0 and 1: ' + options.snapToZeroThreshold );

      // Dimensions for the rest of the stove, dependent on the specified stove width.  Empirically determined, and could
      // be made into options if needed.
      const height = DEFAULT_WIDTH * 0.75;
      const burnerOpeningHeight = DEFAULT_WIDTH * HeaterCoolerBack.OPENING_HEIGHT_SCALE;
      const bottomWidth = DEFAULT_WIDTH * 0.80;

      // Create the body of the stove.
      const stoveBodyShape = new Shape()
        .ellipticalArc( DEFAULT_WIDTH / 2, burnerOpeningHeight / 4, DEFAULT_WIDTH / 2, burnerOpeningHeight / 2, 0, 0, Math.PI, false )
        .lineTo( ( DEFAULT_WIDTH - bottomWidth ) / 2, height + burnerOpeningHeight / 2 )
        .ellipticalArc( DEFAULT_WIDTH / 2, height + burnerOpeningHeight / 4, bottomWidth / 2, burnerOpeningHeight,
          0, Math.PI, 0, true ).lineTo( DEFAULT_WIDTH, burnerOpeningHeight / 2 );

      const stoveBody = new Path( stoveBodyShape, {
        stroke: 'black',
        fill: new LinearGradient( 0, 0, DEFAULT_WIDTH, 0 )
          .addColorStop( 0, Color.toColor( options.baseColor ).brighterColor( 0.5 ) )
          .addColorStop( 1, Color.toColor( options.baseColor ).darkerColor( 0.5 ) )
      } );

      // @private {BooleanProperty}
      this.snapToZeroProperty = new BooleanProperty( options.snapToZero, {
        tandem: options.tandem.createTandem( 'snapToZeroProperty' ),
        phetioDocumentation: 'whether the slider will snap to the off position when released',
        phetioFeatured: true
      } );

      const setSliderToZero = () => {
        heatCoolAmountProperty.set( 0 );
      };
      const sliderRange = new Range( options.coolEnabled ? -1 : 0, options.heatEnabled ? 1 : 0 );

      /**
       * determines if the slider is close enough to zero to snap to zero (even when snapToZeroProperty is false). It's
       * only applicable when both heating and cooling are enabled because that is the only configuration where it was
       * difficult for a user to set the slider to 0. This feature was requested by designers, see https://github.com/phetsims/scenery-phet/issues/568.
       * @returns {boolean}
       */
      const sliderIsCloseToZero = () => {
        return options.coolEnabled && options.heatEnabled && (
          heatCoolAmountProperty.value < 0 && heatCoolAmountProperty.value / sliderRange.min < options.snapToZeroThreshold ||
          heatCoolAmountProperty.value > 0 && heatCoolAmountProperty.value / sliderRange.max < options.snapToZeroThreshold );
      };

      const sliderOptions = merge( {
        thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
        thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
        thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
        thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
        thumbFill: options.thumbFill,
        thumbSize: options.thumbSize,
        thumbFillHighlighted: options.thumbFillHighlighted,
        endDrag: () => {
          if ( this.snapToZeroProperty.value || sliderIsCloseToZero() ) {
            setSliderToZero();
          }
        },
        centerY: stoveBody.centerY,
        right: stoveBody.right - DEFAULT_WIDTH / 8,
        tandem: options.tandem.createTandem( 'slider' )
      }, options.sliderOptions );

      // @public (read-only), please use judiciously, see https://github.com/phetsims/scenery-phet/issues/442
      this.slider = new VSlider(
        heatCoolAmountProperty,
        sliderRange,
        sliderOptions
      );

      // Create the tick labels.
      const labelOptions = {
        font: options.labelFont,
        maxWidth: options.labelMaxWidth
      };
      if ( options.heatEnabled ) { this.slider.addMajorTick( 1, new Text( options.heatString, labelOptions ) ); }
      this.slider.addMinorTick( 0 );
      if ( options.coolEnabled ) { this.slider.addMajorTick( -1, new Text( options.coolString, labelOptions ) ); }

      this.addChild( stoveBody );
      this.addChild( this.slider );

      this.mutate( options );

      // update the back component if provided
      if ( options.heaterCoolerBack ) {
        this.on( 'opacity', () => {
          options.heaterCoolerBack.opacity = this.opacity;
        } );
        this.on( 'pickability', () => {
          options.heaterCoolerBack.pickable = this.pickable;
        } );
        this.on( 'visibility', () => {
          options.heaterCoolerBack.visible = this.visible;
        } );
      }

      // return the slider to its origin if snapToZero is changed to true
      this.snapToZeroProperty.link( snapToZero => {
        snapToZero && setSliderToZero();
      } );
    }
  }

  return sceneryPhet.register( 'HeaterCoolerFront', HeaterCoolerFront );
} );
<|endoftext|>


<commit_before>/// <reference path="bootstrap.js" />

// Variables
var	colors=	['SlateGray', 'RoyalBlue', 'SeaGreen', 'Violet', 'Coral'];
var	n=	0;
var	blankEntry;

// Called when the page is loaded up
$(document).ready(function()
{
	$(".entry")[0].graphRef;
	$(".entry")[0].gliderRefs=	new Array();
	$(".entry")[0].color=	nextColor();
	$(".entry")[0].dashed=	false;
	$(".entry").draggable({ disabled: true, containment:'document' }); // only want to drag in grabber.
	blankEntry=	$(".entry");
	$("#m-entry").graphRef; // not sure what this is for??

	entryFocusMath($(".entry")[0]);
	displayColorToEntry($(".entry"));

	$(".myForm").on("click", ".btn-add", onAddClick
	).on("click", ".btn-remove", onRemoveClick
	).on("click", ".map", onMapClick
	).on("click", ".showColor", onShowColorClick
    ).on("mouseover", ".grabber", makeDraggable // TODO: make touch friendly
    ).on("mouseout", ".grabber", makeUnDraggable // TODO: make touch friendly
	).on("click", ".dashed", onDashedClick
	).on("click", ".mathinput", onMathInputClick
    ).on("click", ".drawer1", onDrawerClick
	).on("keyup", ".entry", onEntryKeyUp
	).on("click", ".collapse-entry", onCollapseEntryClick);
	$("#addNewEntry").on("click", onNewEntryClick);

	$("#m-entry").on("keyup", "textarea", onMobileEntryKeyUp);  // redesign?? tow onkeyups will be cumbersome
});

// Gets the next color in the array of stored colors
function nextColor()
{
	n++;
	if(n=== colors.length)
		n=	0;

	return colors[n];
}

// Displays the color to the entry loader thingy mabober... NOTE: What is the official name to that thing?
// John Says - huh - you mean the color indicator ?
function displayColorToEntry(entry)
{
    entry.find(".showColor").css({ 'background-color': entry[0].color });
}

// Focuses on mathquill's textarea
function entryFocusMath(inputEntry)
{
	$(inputEntry).find(".mathquill-editable:first").addClass('hasCursor').focus();
	$(inputEntry).find(".mathquill-editable:first").find('textarea').focus();
}

// Removes the given object from the graph
function removeFromGraph(inputObj)
{
	if(typeof inputObj=== "object")
	{
		board.removeObject(inputObj.graphRef);
	}
}

// Renders the graph
function renderGraph(inputObj)
{
	// Variables
	var	userFunction;
	var txt = $(inputObj).find(".mathquill-editable").mathquill("text");
	txt = fixInput(txt, inputObj).toLowerCase();
	console.log(txt.substring(0, 7));
	if (txt.indexOf(",") !== -1 ){
	    // if there is a comma try to plot a point
	    // try to render text ((3,2)) as an array [3,2] using the eval below
        // TODO: streamline this
	    var modifiedTxt = eval( txt.replace("((", "[").replace("))", "]") );
	    try {
	        if(JXG.isArray(modifiedTxt)){
	            removeFromGraph(inputObj);
	            inputObj.graphRef = board.create("point", modifiedTxt,
                {
                    visible: true,
                    strokeColor: inputObj.color,
                    fillColor: inputObj.color,
                    fixed: true
                });
	        }

	    } catch (e) {
	        console.log("with comma " + e);
	    }

	} else if (txt.substring(0,7)==="l*i*n*e") {
	    //console.log(txt[8] + "," + txt[10]);

	    try {
	        var pt1 = board.select(txt[8].toUpperCase());
	        var pt2 = board.select(txt[10].toUpperCase());

	        if (JXG.isPoint(pt1) && JXG.isPoint(pt2)) {
	            removeFromGraph(inputObj);
	            inputObj.graphRef = board.create("line", [pt1,pt2],
                {
                    visible: true,
                    strokeWidth: 2,
                    strokeColor: inputObj.color
                });
	        }
	    }
	    catch (e) { console.log("caught " + e); }

	} else {
	    try {
	        eval("userFunction= function(x) { with(Math) return " + mathjs(txt) + " }");
	        if (JXG.isFunction(userFunction)) {
	            removeFromGraph(inputObj);
	            inputObj.graphRef = board.create("functiongraph", userFunction,
                {
                    visible: true,
                    strokeWidth: 2,
                    strokeColor: inputObj.color
                });
	        }
	    }
	    catch (e) { console.log("caught " + e); }
	}

}

// Renders the graph for the mobile view
// Mobile should use the same rendering function, why does it need to be different??
function renderGraphMobile(inputObj)
{
	// Variables
	var	userFunction;
	var	txt=	$(inputObj).find("textarea").val().toLowerCase();
	var	convertedMath=	mathjs(txt);

	try
	{
		eval("userFunction= function(x) { with(Math) return "+convertedMath+" }");
		if(JXG.isFunction(userFunction))
		{
			removeFromGraph(inputObj);
			inputObj.graphRef=	board.create("functiongraph", userFunction,
			{
				visible:	true,
				strokeWidth:	2,
				strokeColor:	"blue"
			});
		}
	}
	catch(e)	{	console.log("caught "+e);	}
}

// Fixes up mathquill derp ups
function fixInput(txt, entry)
{
	// Variables
	var	n=	txt.length;

	txt.toLowerCase();

	if(n> 4)
	{
		// Variables
		var	last3=	txt.substring(n-5, n);

		switch(last3)
		{
			case "s*i*n":	case "c*o*s":	case "t*a*n":	case "q*r*t":
				$(entry).find(".mathquill-editable").mathquill("write", "\\left( x \\right)");
				entryFocusMath(entry);
				break;
		}
	}

	txt=	$(entry).find(".mathquill-editable").mathquill("text");
	txt=	txt.replace("**", "^");
	txt=	txt.replace("s*i*n*", "sin");
	txt=	txt.replace("c*o*s*", "cos");
	txt=	txt.replace("cosh*", "cosh");
	txt=	txt.replace("t*a*n*", "tan");
	txt=	txt.replace("tan*h", "tanh");
	txt=	txt.replace("s*q*r*t", "sqrt");
	txt=	txt.replace("p*i", "pi");
	txt = txt.replace("xcdot", "");

	return txt;
}

// Constrains the given entry to the given element
function constrainTo(entry, elem)
{
	if(entry.offset().left< 0)
		entry.offset({left: 0});
	if(entry.offset().left+entry.width()> $(elem).width())
		entry.offset({left: $(elem).width()-entry.width()});
	if(entry.offset().top< 0)
		entry.offset({top: 0});
	if(entry.offset().top+entry.height()> $(elem).height())
		entry.offset({top: $(elem).height()-entry.height()});
}

// Creates a new entry, despite clicking on the entry
function onNewEntryClick(e)
{
	// Variables
	var	newEntry;
	var	lastEntry=	null;

	if($(".entry").length> 0)
		lastEntry=	$($(".entry")[$(".entry").length-1]);
	newEntry=	$(blankEntry.clone()).appendTo($(".myForm"));
	constructNewEntry(newEntry, lastEntry);
}

// Constructs the new entry
function constructNewEntry(newEntry, lastEntry)
{
	if(lastEntry!= null)
		newEntry.offset({top: lastEntry.offset().top+lastEntry.height()+36, left: lastEntry.offset().left});
	if(newEntry.offset().top+newEntry.height()> $(window).height())
	{
		// Variables
		var	offset=	newEntry.offset();

		newEntry.offset({top: 85, left: offset.left+64, right: offset.right, bottom: offset.bottom});
	}
	newEntry[0].color= nextColor();
	newEntry.find('.mathquill-editable').html(" ").mathquill('editable');
	displayColorToEntry(newEntry);
	newEntry.find('.dashed').css({ color: "LightGray" });
	newEntry.draggable({ disabled: true, containment:'document' });  // TODO: change to mobile friendly
	newEntry.find(".mathquill-editable:first").addClass('hasCursor').find('textarea').focus();

	return newEntry;
}

// Called when the add button has been clicked
function onAddClick(e)
{
    // Variables
	var controlForm=	$(".myForm");
	var	currentEntry=	$(this).parents(".entry");
	var	newEntry= 	$(currentEntry.clone()).appendTo(controlForm);

	newEntry=	constructNewEntry(newEntry);

	// change the button faces
	controlForm.find('.entry:not(:last) .btn-add'
	).removeClass('btn-add').addClass('btn-remove'
	).html('');
}

// Called when the remove button has been called
function onRemoveClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");
	var	locations=	new Array();

	removeFromGraph(currEntry[0]);
	$(".entry").each(function(index, elem)
	{
		if(elem=== currEntry[0])
			return;

		locations.push($(elem).offset());
	});
	currEntry.remove();
	$(".entry").each(function(index, elem)
	{
		$(elem).offset(locations[index]);
	});
}

// Called whenever the user changes the color of the equation
function onShowColorClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");

	currEntry[0].color=	nextColor();
	if (currEntry[0].graphRef)
	    (currEntry[0].graphRef).setProperty({ strokeColor: currEntry[0].color });
	    if (JXG.isPoint(currEntry[0].graphRef))
	        (currEntry[0].graphRef).setProperty({ fillColor: currEntry[0].color });

	try{
	    for(var i= 0; i< currEntry[0].gliderRefs.length; i++)
	        currEntry[0].gliderRefs[i].setProperty({color: currEntry[0].color});
	} catch (e) {
        // sometimes there are not any gliders and length is undefined.
	    //console.log(e);
	}

	displayColorToEntry(currEntry);
}
// Called whenever the drawer button is clicked
function onDrawerClick(e) {
    // Variables
    console.log("hi");
    var currEntry = $(this).parents(".entry");
    currEntry.find(".collapse").collapse("toggle");

}
// enable entry dragging.
function makeDraggable(e) {
    $(this).parents(".entry").draggable({ disabled: false });
}
function makeUnDraggable(e) {
    $(this).parents(".entry").draggable({ disabled: true });
}

// Called whenever the user wants to toggle between a dashed equation
function onDashedClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");

	if(currEntry[0].graphRef)
	{
		if(currEntry[0].dashed)
		{
			(currEntry[0].graphRef).setProperty({dash: 0});
			currEntry[0].dashed=	false;
			currEntry.find(".dashed").css({color: "LightGray"});
		}
		else
		{
			(currEntry[0].graphRef).setProperty({dash: 2});
			currEntry[0].dashed=	true;
			currEntry.find(".dashed").css({color: "Black"});
		}
	}
	else
		currEntry.effect("shake", {times: 2}, 700);
}

// Called when the map button is clicked
function onMapClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");

	if(currEntry[0].graphRef)
		currEntry[0].gliderRefs.push(board.create("glider", [0, 0, currEntry[0].graphRef], {color: currEntry[0].color}));
	else
		currEntry.effect("shake", {times: 2}, 700);  // not sure this adds value
}

// Called whenever the user clicks on the textbox
function onMathInputClick(e)
{
	// Variables
	var	currEntry=	$(e.target).parents(".entry")[0];

	entryFocusMath(currEntry);
}

// Called whenever there is a key detected on an entry input
function onEntryKeyUp(e)
{
	// Variables
	var	currEntry=	$(e.target).parents(".entry")[0];

	if(e.keyCode=== 13)
	{
		onNewEntryClick(e);
	}

	renderGraph(currEntry);

	if(currEntry.dashed)
	{
		$(currEntry).find(".dashed").click().click();
	}
}

// Called whenever there is a key detected on the mobile entry input
// Streamline with one render graph function
function onMobileEntryKeyUp(e)
{
	// Variables
	var	currEntry=	$(e.target).parents("#m-entry")[0];

	renderGraphMobile(currEntry);
}

// Called whenever the collapse entry button has been clicked
function onCollapseEntryClick(e)
{
    var currEntry = $(e.target).parents(".entry")[0];

    if (currEntry.oldLeft) {
        $(currEntry).animate({ left: currEntry.oldLeft }, "fast");
		$(e.target).find(".glyphicon").removeClass("glyphicon-menu-right").addClass("glyphicon-menu-left");
        currEntry.oldLeft = 0;
        //slide back should be constrained to the window.
    } else {
        console.log($(currEntry).offset().left);
		if($(currEntry).offset().left> 0)
			currEntry.oldLeft = $(currEntry).offset().left;
		else
			currEntry.oldLeft=	1;
        $(currEntry).animate({ left: "-340px" }, "fast");
		$(e.target).find(".glyphicon").removeClass("glyphicon-menu-left").addClass("glyphicon-menu-right");
        //-340px should be dependent on the size of Entry really
        //TODO change the glyphicon to right
    }
    /*
	// Variables
	var	currEntry=	$(e.target).parents(".entry")[0];
	var	listbox=	$(".sideListbox")[0];

	listbox.items.push(currEntry);
	$(currEntry).hide();
	updateListbox();
    */
}

/*

// Called when one of the listbox items clicked on the entry
function uncollapseEntry(elem)
{
	// Variables
	var	listbox=	$(".sideListbox")[0];

	for(var i= 0; i< listbox.items.length; i++)
	{
		if(listbox.items[i]=== $(elem).parents(".item")[0].refEntry)
		{
			$($(elem).parents(".item")[0].refEntry).show();
			listbox.items=	removeFromArray(i, listbox.items);
			updateListbox();

			return;
		}
	}
}

// Removes the given index from the array
function removeFromArray(index, list)
{
	// Variables
	var	result=	new Array();

	for(var i= 0; i< list.length; i++)
	{
		if(i!= index)
			result.push(list[i]);
	}

	return result;
}

// Updates the listbox of entries
function updateListbox()
{
	// Variables
	var	listbox=	$(".sideListbox")[0];
	var	str=	"";

	if(listbox.items.length== 0)
		$(listbox).hide();
	else
		$(listbox).show();

	for(var i= 0; i< listbox.items.length; i++)
	{
		str+=	"
";
		str+=	"";
		str+=	"";
		str+=	"";
		str+=	"
";
	}
	$(listbox).html(str);
	$(listbox).find(".item").each(function(index, elem)
	{
		elem.refEntry=	listbox.items[index];
	});
}*/

// End of File<commit_msg>Last fix for the next couple of hours<commit_after>/// <reference path="bootstrap.js" />

// Variables
var	colors=	['SlateGray', 'RoyalBlue', 'SeaGreen', 'Violet', 'Coral'];
var	n=	0;
var	blankEntry;

// Called when the page is loaded up
$(document).ready(function()
{
	$(".entry")[0].graphRef;
	$(".entry")[0].gliderRefs=	new Array();
	$(".entry")[0].color=	nextColor();
	$(".entry")[0].dashed=	false;
	$(".entry").draggable({ disabled: true, containment:'document' }); // only want to drag in grabber.
	blankEntry=	$(".entry");
	$("#m-entry").graphRef; // not sure what this is for??

	entryFocusMath($(".entry")[0]);
	displayColorToEntry($(".entry"));

	$(".myForm").on("click", ".btn-remove", onRemoveClick
	).on("click", ".map", onMapClick
	).on("click", ".showColor", onShowColorClick
    ).on("mouseover", ".grabber", makeDraggable // TODO: make touch friendly
    ).on("mouseout", ".grabber", makeUnDraggable // TODO: make touch friendly
	).on("click", ".dashed", onDashedClick
	).on("click", ".mathinput", onMathInputClick
    ).on("click", ".drawer1", onDrawerClick
	).on("keyup", ".entry", onEntryKeyUp
	).on("click", ".collapse-entry", onCollapseEntryClick);
	$("#addNewEntry").on("click", onNewEntryClick);

	$("#m-entry").on("keyup", "textarea", onMobileEntryKeyUp);  // redesign?? tow onkeyups will be cumbersome
});

// Gets the next color in the array of stored colors
function nextColor()
{
	n++;
	if(n=== colors.length)
		n=	0;

	return colors[n];
}

// Displays the color to the entry loader thingy mabober... NOTE: What is the official name to that thing?
// John Says - huh - you mean the color indicator ?
function displayColorToEntry(entry)
{
    entry.find(".showColor").css({ 'background-color': entry[0].color });
}

// Focuses on mathquill's textarea
function entryFocusMath(inputEntry)
{
	$(inputEntry).find(".mathquill-editable:first").addClass('hasCursor').focus();
	$(inputEntry).find(".mathquill-editable:first").find('textarea').focus();
}

// Removes the given object from the graph
function removeFromGraph(inputObj)
{
	if(typeof inputObj=== "object")
	{
		board.removeObject(inputObj.graphRef);
	}
}

// Renders the graph
function renderGraph(inputObj)
{
	// Variables
	var	userFunction;
	var txt = $(inputObj).find(".mathquill-editable").mathquill("text");
	txt = fixInput(txt, inputObj).toLowerCase();
	console.log(txt.substring(0, 7));
	if (txt.indexOf(",") !== -1 ){
	    // if there is a comma try to plot a point
	    // try to render text ((3,2)) as an array [3,2] using the eval below
        // TODO: streamline this
	    var modifiedTxt = eval( txt.replace("((", "[").replace("))", "]") );
	    try {
	        if(JXG.isArray(modifiedTxt)){
	            removeFromGraph(inputObj);
	            inputObj.graphRef = board.create("point", modifiedTxt,
                {
                    visible: true,
                    strokeColor: inputObj.color,
                    fillColor: inputObj.color,
                    fixed: true
                });
	        }

	    } catch (e) {
	        console.log("with comma " + e);
	    }

	} else if (txt.substring(0,7)==="l*i*n*e") {
	    //console.log(txt[8] + "," + txt[10]);

	    try {
	        var pt1 = board.select(txt[8].toUpperCase());
	        var pt2 = board.select(txt[10].toUpperCase());

	        if (JXG.isPoint(pt1) && JXG.isPoint(pt2)) {
	            removeFromGraph(inputObj);
	            inputObj.graphRef = board.create("line", [pt1,pt2],
                {
                    visible: true,
                    strokeWidth: 2,
                    strokeColor: inputObj.color
                });
	        }
	    }
	    catch (e) { console.log("caught " + e); }

	} else {
	    try {
	        eval("userFunction= function(x) { with(Math) return " + mathjs(txt) + " }");
	        if (JXG.isFunction(userFunction)) {
	            removeFromGraph(inputObj);
	            inputObj.graphRef = board.create("functiongraph", userFunction,
                {
                    visible: true,
                    strokeWidth: 2,
                    strokeColor: inputObj.color
                });
	        }
	    }
	    catch (e) { console.log("caught " + e); }
	}

}

// Renders the graph for the mobile view
// Mobile should use the same rendering function, why does it need to be different??
function renderGraphMobile(inputObj)
{
	// Variables
	var	userFunction;
	var	txt=	$(inputObj).find("textarea").val().toLowerCase();
	var	convertedMath=	mathjs(txt);

	try
	{
		eval("userFunction= function(x) { with(Math) return "+convertedMath+" }");
		if(JXG.isFunction(userFunction))
		{
			removeFromGraph(inputObj);
			inputObj.graphRef=	board.create("functiongraph", userFunction,
			{
				visible:	true,
				strokeWidth:	2,
				strokeColor:	"blue"
			});
		}
	}
	catch(e)	{	console.log("caught "+e);	}
}

// Fixes up mathquill derp ups
function fixInput(txt, entry)
{
	// Variables
	var	n=	txt.length;

	txt.toLowerCase();

	if(n> 4)
	{
		// Variables
		var	last3=	txt.substring(n-5, n);

		switch(last3)
		{
			case "s*i*n":	case "c*o*s":	case "t*a*n":	case "q*r*t":
				$(entry).find(".mathquill-editable").mathquill("write", "\\left( x \\right)");
				entryFocusMath(entry);
				break;
		}
	}

	txt=	$(entry).find(".mathquill-editable").mathquill("text");
	txt=	txt.replace("**", "^");
	txt=	txt.replace("s*i*n*", "sin");
	txt=	txt.replace("c*o*s*", "cos");
	txt=	txt.replace("cosh*", "cosh");
	txt=	txt.replace("t*a*n*", "tan");
	txt=	txt.replace("tan*h", "tanh");
	txt=	txt.replace("s*q*r*t", "sqrt");
	txt=	txt.replace("p*i", "pi");
	txt = txt.replace("xcdot", "");

	return txt;
}

// Creates a new entry, despite clicking on the entry
function onNewEntryClick(e)
{
	// Variables
	var	newEntry;
	var	lastEntry=	null;

	if($(".entry").length> 0)
		lastEntry=	$($(".entry")[$(".entry").length-1]);
	newEntry=	$(blankEntry.clone()).appendTo($(".myForm"));
	constructNewEntry(newEntry, lastEntry);
}

// Constructs the new entry
function constructNewEntry(newEntry, lastEntry)
{
	if(lastEntry!= null)
		newEntry.offset({top: lastEntry.offset().top+lastEntry.height()+36, left: lastEntry.offset().left});
	if(newEntry.offset().top+newEntry.height()> $(window).height())
	{
		// Variables
		var	offset=	newEntry.offset();

		newEntry.offset({top: 85, left: offset.left+64, right: offset.right, bottom: offset.bottom});
	}
	if(newEntry.offset().left< 0)
	{
		newEntry.offset({left: 1});
		//newEntry.find(".collapse-entry").find(".glyphicon").removeClass("glyphicon-menu-right").addClass("glyphicon-menu-left");
	}
	newEntry[0].color= nextColor();
	newEntry.find('.mathquill-editable').html(" ").mathquill('editable');
	displayColorToEntry(newEntry);
	newEntry.find('.dashed').css({ color: "LightGray" });
	newEntry.draggable({ disabled: true, containment:'document' });  // TODO: change to mobile friendly
	newEntry.find(".mathquill-editable:first").addClass('hasCursor').find('textarea').focus();

	return newEntry;
}

// Called when the remove button has been called
function onRemoveClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");
	var	locations=	new Array();

	removeFromGraph(currEntry[0]);
	$(".entry").each(function(index, elem)
	{
		if(elem=== currEntry[0])
			return;

		locations.push($(elem).offset());
	});
	currEntry.remove();
	$(".entry").each(function(index, elem)
	{
		$(elem).offset(locations[index]);
	});
}

// Called whenever the user changes the color of the equation
function onShowColorClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");

	currEntry[0].color=	nextColor();
	if (currEntry[0].graphRef)
	    (currEntry[0].graphRef).setProperty({ strokeColor: currEntry[0].color });
	    if (JXG.isPoint(currEntry[0].graphRef))
	        (currEntry[0].graphRef).setProperty({ fillColor: currEntry[0].color });

	try{
	    for(var i= 0; i< currEntry[0].gliderRefs.length; i++)
	        currEntry[0].gliderRefs[i].setProperty({color: currEntry[0].color});
	} catch (e) {
        // sometimes there are not any gliders and length is undefined.
	    //console.log(e);
	}

	displayColorToEntry(currEntry);
}
// Called whenever the drawer button is clicked
function onDrawerClick(e) {
    // Variables
    console.log("hi");
    var currEntry = $(this).parents(".entry");
    currEntry.find(".collapse").collapse("toggle");

}
// enable entry dragging.
function makeDraggable(e) {
    $(this).parents(".entry").draggable({ disabled: false });
}
function makeUnDraggable(e) {
    $(this).parents(".entry").draggable({ disabled: true });
}

// Called whenever the user wants to toggle between a dashed equation
function onDashedClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");

	if(currEntry[0].graphRef)
	{
		if(currEntry[0].dashed)
		{
			(currEntry[0].graphRef).setProperty({dash: 0});
			currEntry[0].dashed=	false;
			currEntry.find(".dashed").css({color: "LightGray"});
		}
		else
		{
			(currEntry[0].graphRef).setProperty({dash: 2});
			currEntry[0].dashed=	true;
			currEntry.find(".dashed").css({color: "Black"});
		}
	}
	else
		currEntry.effect("shake", {times: 2}, 700);
}

// Called when the map button is clicked
function onMapClick(e)
{
	// Variables
	var	currEntry=	$(this).parents(".entry");

	if(currEntry[0].graphRef)
		currEntry[0].gliderRefs.push(board.create("glider", [0, 0, currEntry[0].graphRef], {color: currEntry[0].color}));
	else
		currEntry.effect("shake", {times: 2}, 700);  // not sure this adds value
}

// Called whenever the user clicks on the textbox
function onMathInputClick(e)
{
	// Variables
	var	currEntry=	$(e.target).parents(".entry")[0];

	entryFocusMath(currEntry);
}

// Called whenever there is a key detected on an entry input
function onEntryKeyUp(e)
{
	// Variables
	var	currEntry=	$(e.target).parents(".entry")[0];

	if(e.keyCode=== 13)
	{
		onNewEntryClick(e);
	}

	renderGraph(currEntry);

	if(currEntry.dashed)
	{
		$(currEntry).find(".dashed").click().click();
	}
}

// Called whenever there is a key detected on the mobile entry input
// Streamline with one render graph function
function onMobileEntryKeyUp(e)
{
	// Variables
	var	currEntry=	$(e.target).parents("#m-entry")[0];

	renderGraphMobile(currEntry);
}

// Called whenever the collapse entry button has been clicked
function onCollapseEntryClick(e)
{
    var currEntry = $(e.target).parents(".entry")[0];

    if (currEntry.oldLeft) {
        $(currEntry).animate({ left: currEntry.oldLeft }, "fast");
		$(e.target).find(".glyphicon").removeClass("glyphicon-menu-right").addClass("glyphicon-menu-left"); // Somethings wrong with the changes for some reason
        currEntry.oldLeft = 0;
        //slide back should be constrained to the window.
    } else {
        console.log($(currEntry).offset().left);
		if($(currEntry).offset().left> 0)
			currEntry.oldLeft = $(currEntry).offset().left;
		else
			currEntry.oldLeft=	1;
        $(currEntry).animate({ left: "-340px" }, "fast");
		$(e.target).find(".glyphicon").removeClass("glyphicon-menu-left").addClass("glyphicon-menu-right");
        //-340px should be dependent on the size of Entry really
        //TODO change the glyphicon to right
    }
}

// End of File<|endoftext|>


<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * Unit tests for PhetioObject
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var PhetioObject = require( 'TANDEM/PhetioObject' );
  var Tandem = require( 'TANDEM/Tandem' );
  require( 'ifphetio!PHET_IO/phetio' );

  QUnit.module( 'PhetioObject' );

  // TODO: should we use phetioInherit?
  var MockTypeIO = function( instance, phetioID ) {};
  MockTypeIO.typeName = 'MockTypeIO';
  MockTypeIO.events = [ 'hello' ];
  MockTypeIO.documentation = 'mock type';
  MockTypeIO.methods = {};
  MockTypeIO.supertype = window.Object;
  MockTypeIO.allEvents = [ 'hello' ];

  QUnit.test( 'PhetioObject start/start', function( assert ) {
    assert.ok( true, 'initial test' );

    var obj = new PhetioObject( {
      tandem: Tandem.rootTandem,
      phetioType: MockTypeIO
    } );
    obj.startEvent( 'model', 'hello' );
    window.assert && assert.throws( function() {
      obj.startEvent( 'model', 'hello' );
    }, 'Should throw an assertion error when starting event twice' );
  } );

  QUnit.test( 'PhetioObject start/end', function( assert ) {
    assert.ok( true, 'initial test' );

    var obj = new PhetioObject( {
      tandem: Tandem.rootTandem.createTandem( 'test1' ),
      phetioType: MockTypeIO
    } );
    obj.startEvent( 'model', 'hello' );
    obj.endEvent();
  } );

  QUnit.test( 'PhetioObject end without start', function( assert ) {
    assert.ok( true, 'initial test' );

    var obj = new PhetioObject( {
      tandem: Tandem.rootTandem.createTandem( 'test2' ),
      phetioType: MockTypeIO
    } );
    window.assert && assert.throws( function() {
      obj.endEvent( 'model', 'hello' );
    }, 'Should throw an assertion error when Ending an unstarted event' );
  } );
} );<commit_msg>Removed options for endEvent, see https://github.com/phetsims/tandem/issues/47<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * Unit tests for PhetioObject
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var PhetioObject = require( 'TANDEM/PhetioObject' );
  var Tandem = require( 'TANDEM/Tandem' );
  require( 'ifphetio!PHET_IO/phetio' );

  QUnit.module( 'PhetioObject' );

  // TODO: should we use phetioInherit?
  var MockTypeIO = function( instance, phetioID ) {};
  MockTypeIO.typeName = 'MockTypeIO';
  MockTypeIO.events = [ 'hello' ];
  MockTypeIO.documentation = 'mock type';
  MockTypeIO.methods = {};
  MockTypeIO.supertype = window.Object;
  MockTypeIO.allEvents = [ 'hello' ];

  QUnit.test( 'PhetioObject start/start', function( assert ) {
    assert.ok( true, 'initial test' );

    var obj = new PhetioObject( {
      tandem: Tandem.rootTandem,
      phetioType: MockTypeIO
    } );
    obj.startEvent( 'model', 'hello' );
    window.assert && assert.throws( function() {
      obj.startEvent( 'model', 'hello' );
    }, 'Should throw an assertion error when starting event twice' );
  } );

  QUnit.test( 'PhetioObject start/end', function( assert ) {
    assert.ok( true, 'initial test' );

    var obj = new PhetioObject( {
      tandem: Tandem.rootTandem.createTandem( 'test1' ),
      phetioType: MockTypeIO
    } );
    obj.startEvent( 'model', 'hello' );
    obj.endEvent();
  } );

  QUnit.test( 'PhetioObject end without start', function( assert ) {
    assert.ok( true, 'initial test' );

    var obj = new PhetioObject( {
      tandem: Tandem.rootTandem.createTandem( 'test2' ),
      phetioType: MockTypeIO
    } );
    window.assert && assert.throws( function() {
      obj.endEvent();
    }, 'Should throw an assertion error when Ending an unstarted event' );
  } );
} );<|endoftext|>


<commit_before>/* ===========================================================
 * bootstrap-popover.js v2.0.0
 * http://twitter.github.com/bootstrap/javascript.html#popover
 * ===========================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================================================== */

!function( $ ) {

 "use strict"

  var Popover = function ( element, options ) {
    this.init('popover', element, options)
  }

  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
     ========================================== */

  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {

    constructor: Popover

  , setContent: function () {
      var $tip = this.tip()
        , title = this.getTitle()
        , content = this.getContent()

      $tip.find('.title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
      $tip.find('.content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)

      $tip[0].className = 'popover'
    }

  , hasContent: function () {
      return this.getTitle() || this.getContent()
    }

  , getContent: function () {
      var content
        , $e = this.$element
        , o = this.options

      content = $e.attr('data-content')
        || (typeof o.content == 'function' ? o.content.call($e[0]) :  o.content)

      content = content.toString().replace(/(^\s*|\s*$)/, "")

      return content
    }

  , tip: function() {
      if (!this.$tip) {
        this.$tip = $(this.options.template)
      }
      return this.$tip
    }

  })

 /* POPOVER PLUGIN DEFINITION
  * ======================= */

  $.fn.popover = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('popover')
        , options = typeof option == 'object' && option
      if (!data) $this.data('popover', (data = new Popover(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.popover.Constructor = Popover

  $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
    placement: 'right'
  , content: ''
  , template: '

'
  })

}( window.jQuery )<commit_msg>Make the popove respect custom classes<commit_after>/* ===========================================================
 * bootstrap-popover.js v2.0.0
 * http://twitter.github.com/bootstrap/javascript.html#popover
 * ===========================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================================================== */

!function( $ ) {

 "use strict"

  var Popover = function ( element, options ) {
    this.init('popover', element, options)
  }

  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
     ========================================== */

  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {

    constructor: Popover

  , setContent: function () {
      var $tip = this.tip()
        , title = this.getTitle()
        , content = this.getContent()

      $tip.find('.title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
      $tip.find('.content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)

      $tip.removeClass('fade top bottom left right in')
    }

  , hasContent: function () {
      return this.getTitle() || this.getContent()
    }

  , getContent: function () {
      var content
        , $e = this.$element
        , o = this.options

      content = $e.attr('data-content')
        || (typeof o.content == 'function' ? o.content.call($e[0]) :  o.content)

      content = content.toString().replace(/(^\s*|\s*$)/, "")

      return content
    }

  , tip: function() {
      if (!this.$tip) {
        this.$tip = $(this.options.template)
      }
      return this.$tip
    }

  })

 /* POPOVER PLUGIN DEFINITION
  * ======================= */

  $.fn.popover = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('popover')
        , options = typeof option == 'object' && option
      if (!data) $this.data('popover', (data = new Popover(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.popover.Constructor = Popover

  $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
    placement: 'right'
  , content: ''
  , template: '

'
  })

}( window.jQuery )
<|endoftext|>