PHP Classes

File: jQueryP.class.php

Recommend this page to a friend!
  Classes of Sam S   jQueryP   jQueryP.class.php   Download  
File: jQueryP.class.php
Role: Class source
Content type: text/plain
Description: The class source
Class: jQueryP
Call PHP functions to handle jQuery events
Author: By
Last change:
Date: 14 years ago
Size: 22,405 bytes
 

Contents

Class file image Download
<?php /** * @package jQueryP * * This package contains the tools to enable the manipulation of * client-side elements from the server side using jQuery. * Additionally, the class provides a proxy interface for server side * callbacks on client-side elements using jQuery events * * 04/29/2009 * @author Sam Shull <sam.shull@jhspecialty.com> * @version 0.5 * * @copyright Copyright (c) 2009 Sam Shull <sam.shull@jhspeicalty.com> * @license <http://www.opensource.org/licenses/mit-license.html> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * CHANGES - * */ /**************************************************************************************************** * * ---- NOTICE ----- * * This file must be included before any output occurs or it will not work appropriately * * /****************************************************************************************************/ /** * Enables jQuery like object intialization * * @param mixed $selector * @param string $context * * @return jQueryP */ function jQueryP($selector, $context=null) { return new jQueryP($selector, $context); } /** * This class uses the magic methods to enable ease of access * to jQuery methods from the server side * * NOTE - use the jQueryP::createProxy method if you would like to make a server side callback * * <code> * <html> * <?php * $begin_opacity = 0; * $end_opacity = 0.5; * $first_eleemnt = '0'; * * function scroll () * { * $arguments = func_get_args(); * return 'document.body.innerHTML += "<br>"+' . json_encode(print_r($arguments, true)); * } * * require 'jQueryP.php'; * ?> * <body style="height:6000px;"> * <input id="test" style="display:none"/> * <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> * <script type="text/javascript"> * <?php * print jQueryP(JSVar('window')) * ->bind('scroll', jQueryP::createProxy('scroll')); * * print 'var test = ' . * jQueryP('#test') * //example of PHP bracket notation access to a method * ->{(true ? 'fadeIn' : 'show')}(500) * ->css(array('border' => '1px solid red', 'opacity' => $begin_opacity)) * ->animate(array('opacity' => $end_opacity), 500, JSVar('function(){alert("animation complete");}')) * //example of PHP bracket notation access to a property * ->{0}; // or ->$first_element; or ->{'example' . $first_element}; * //since jQueryP implements ArrayAccess you can also use: * //[0] or [$first_element] or ['example' . $first_element] * //but not for method calls and not after other method calls * //so use curly braces not square brackets in PHP, much better * ?> * </script> * </body> * </html> * </code> */ class jQueryP implements ArrayAccess { /** * Pick which one of the valid HTTP verbs, * preferrably not POST, GET, PUT, OPTIONS, TRACE, HEAD, or DELETE, * that should be used as a way to simplify call back * http://annevankesteren.nl/2007/10/http-methods * * Depends on web server configuration * @const string */ const HTTP_METHOD = 'MKCOL'; /** * the function to use for hashing the callback function * in jQueryP::createProxy * * @const string */ const HASH_FUNCTION = 'md5'; //md5|sha1 /** * String value of the object * * @var string */ public $_str; /** * just an identifier for ensuring that * the jQueryP_ARGUMENTS_ENCODER javascript * function has been included before the * jQueryP::createProxy method is called * * @var boolean */ private static $included = false; /** * * * @var array */ private static $input = array(); /** * Establish the base string * * @param mixed $selector * @param string $context */ public function __construct ($selector, $context=null) { $this->_str = sprintf('jQuery(%s%s)', $selector instanceof JSVar ? $selector : json_encode($selector), ($context ? ",{$context}" : '') ); return; } /** * Magic method simplifies adding methods * onto the string value of the object * * @param string name * @param array $args * * @return jQueryP */ public function __call ($name, array $args) { foreach ($args as $n => $arg) { //if the arg is an instance of JSVar leave it that way $args[$n] = self::js_encode($arg); } $this->_str .= sprintf("\n\t.%s(%s)", $name, implode(',', $args)); return $this; } /** * Get the string value of the object * joined to the desired property * * @return string */ public function __get ($name) { return sprintf("{$this->_str}[%s]\n", json_encode($name)); } /** * Dont try it. Too hard to keep track of * * @throws BadMethodCallException */ public function __set ($name, $value) { throw new BadMethodCallException('The server side jQueryP object has no setters.'); } /** * Get the string value of the object * * @return string */ public function __toString () { return "{$this->_str}\n"; } /** * Get the string value of the object * adds a semi-colon for safety * * @return string */ public function toString ($comma=true) { return $this->_str . ($comma ? ";\n" : "\n"); } /** * Get the string value of the object * joined to the desired property * * @return string */ public function offsetGet ($offset) { return is_numeric($offset) ? "{$this->_str}[{$offset}]\n" : $this->__get($offset); } /** * Dont try it. * * @throws BadMethodCallException */ public function offsetSet ($offset, $value) { throw new BadMethodCallException('The server side jQueryP object has no setters.'); } /** * Dont try it. * * @throws Exception */ public function offsetUnset ($offset) { throw new BadMethodCallException('The server side jQueryP object has no setters.'); } /** * Dont try it. Too hard to keep track of * * @throws Exception */ public function offsetExists ($offset) { throw new BadMethodCallException('The server side jQueryP object has no setters.'); } /** * * * @return mixed */ public static function & extend (&$object) { $args = func_get_args(); if (is_array($object)) { for ($i = 1, $l = count($args);$i < $l; ++$i) { foreach ($args[$i] as $name => $value) { if (isset($object[$name]) && !is_scalar($object[$name]) && !is_scalar($value)) { $object[$name] = self::extend($object[$name], $value); } elseif (!is_null($value)) { $object[$name] = $value; } } } return $object; } for ($i = 1, $l = count($args);$i < $l; ++$i) { foreach ($args[$i] as $name => $value) { if (isset($object->$name) && !is_scalar($object->$name) && !is_scalar($value)) { $object->$name = self::extend($object->$name, $value); } elseif (!is_null($value)) { $object->$name = $value; } } } return $object; } /** * Need special support for encoding JSVar in * into javascript instead of JSON * * @return string */ public static function js_encode ($object) { if ($object instanceof JSVar) { return $object->__toString(); } if (!is_scalar($object)) { $ret = array(); if (is_array($object) && !array_diff(array_keys($object), range(0, count($var) - 1))) { foreach ($object as $property => $value) { $ret[] = ( $value instanceof JSVar ? $value->__toString() : self::js_encode($value) ); } return '[' . implode(',', $ret) . ']'; } foreach ($object as $property => $value) { $ret[] = json_encode($property) . ":" . ( $value instanceof JSVar ? $value->__toString() : self::js_encode($value) ); } return '{' . implode(',', $ret) . '}'; } return json_encode($object); } /** * Returns the jQueryP_ARGUMENTS_ENCODER javascript * function string for inclusion in the script * * @return string */ public static function setupProxies () { self::$included = true; return jQueryP_ARGUMENTS_ENCODER; } /** * * * @return array */ protected static function getInput () { if (self::$input) { return self::$input; } switch ($_SERVER['REQUEST_METHOD']) { case 'GET': { self::$input = $_GET; break; } case 'POST': { self::$input = $_POST; break; } default: { $source = function_exists('stream_get_wrappers') && in_array('php', stream_get_wrappers()) ? file_get_contents('php://input') : ( isset($_SERVER['HTTP_RAW_POST_DATA']) ? $_SERVER['HTTP_RAW_POST_DATA'] : ( isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null ) ); parse_str($source, $output); self::$input = $output; } } return (array)self::$input; } /** * Create a JavaScript proxy to a PHP function. * A javascript success function can be specified to enable different * handling of the server side data on the client side, but the default * is that if the callback returns data it is expected to be executable javascript. * * Element references in the arguments will return a string representation * of a jQuery selector string that will allow you to access that element * * This is not the most efficient way to perform a server side callback, * but it is the easiest to implement into a general use class like this one. * * @param array $options * * @return JSFunction */ public static function createProxy($options) { if (!self::$included) { throw new LogicException('Please call jQueryP::setupProxies before creating a proxy.'); } if ( !isset($options['callback']) || !is_callable($options['callback']) || ( is_object($options['callback']) && !method_exists($options['callback'], '__invoke') ) ) { throw new InvalidArgumentException('jQueryP::createProxy requires that the "callback" element be set in the options argument.'); } if ($options['callback'] instanceof JSVar) { return $options['callback']; } $func = self::HASH_FUNCTION; //create an identifier - always use spl_object_hash on a Closure $hash = is_object($options['callback']) && method_exists($options['callback'], '__invoke') && function_exists('spl_object_hash') ? spl_object_hash($options['callback']) : $func(serialize($options['callback'])); //execute the callback if appropriate if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == jQueryP::HTTP_METHOD) { $input = self::getInput(); //testing server had magic_quotes - i hate magic quotes $magic_quotes = get_magic_quotes_gpc(); if (isset($input['callback'], $input['arguments']) && $hash == ($magic_quotes ? stripslashes($input['callback']) : $input['callback'])) { //get the arguments into an array $args = json_decode($magic_quotes ? stripslashes($input['arguments']) : $input['arguments']); //clean up the output buffer $x = ob_get_clean(); //end the process if the callback is being called die( call_user_func_array( is_object($options['callback']) && method_exists($options['callback'], '__invoke') ? //just in case array($options['callback'], '__invoke') : $options['callback'], is_array($args) ? $args : array($args) ) ); } } unset($options['callback']); $array = array( 'type' => jQueryP::HTTP_METHOD, 'success' => JSVar('function (data, status) { if (status === "success" && jQuery.trim(data+"")) { //if the callback returns data //it is expected to be javascript jQuery.globalEval(data); } }'), 'data' => array( 'callback' => $hash, 'arguments' => JSVar('jQuery._encodeArguments(jQuery.makeArray(arguments))'), ) ); //this is a javascript callback that will send an XHR //to the script that is being requested now ($_SERVER['PHP_SELF']), //allowing this function to execute the callback as shown above return new JSFunction( sprintf('jQuery.ajax(%s);', self::js_encode( self::extend($array, $options) )) ); } } /** * A utility function * * */ function JSVar ($v) { return new JSVar($v); } /** * Ensure that data is stored as javascript * not JSON * * */ class JSVar extends jQueryP { /** * * * */ public function __construct($var) { $this->_str = $var; } /** * * * */ public function __toString() { return (string)$this->_str; } } /** * A utility function * * */ function JSFunction ($args=null, $code=null) { if (is_null($code)) { $code = $args; $args = null; } return new JSFunction($args, $code); } /** * Create an anonymous javascript function * from a code string and optional argument * string * * */ class JSFunction extends JSVar { /** * * * */ public function __construct($args, $code=null) { if (is_null($code)) { $code = $args; $args = null; } if (is_array($args)) { foreach ($args as $n => $arg) { //if the arg is an instance of JSVar leave it that way $args[$n] = jQueryP::js_encode($arg); } $args = implode(",", $args); } $this->_str = "function({$args}){{$code}}"; } } /** * A utility function for calling a utility class * * */ function JSScopingFunction ($args=null, $code=null) { if (is_null($code)) { $code = $args; $args = null; } return new JSScopingFunction($args, $code); } /** * A utility class for storing information that must NOT be json encoded * * */ class JSScopingFunction extends JSVar { /** * * * */ public function __construct($args=null, $code=null) { if (is_null($code)) { $code = $args; $args = null; } if (is_array($args)) { foreach ($args as $n => $arg) { //if the arg is an instance of JSVar leave it that way $args[$n] = jQueryP::js_encode($arg); } $args = implode(",", $args); } $this->_str = "(function(){{$code}})({$args})"; } } /** * A utility function for calling a utility class * * */ function JSTimeout ($func, $time=13) { return new JSTimeout($func, $time); } /** * A utility class for storing information that must NOT be json encoded * * */ class JSTimeout extends JSVar { /** * * * */ public function __construct($func, $time=13) { $this->_str = "setTimeout({$func},{$time})"; } } /** * JSON.stringify with support for element selectors * for use by a jQueryP_PROXY_FUNCTION to encode data * */ define('jQueryP_ARGUMENTS_ENCODER', <<<EOD jQuery._encodeArguments = function (args) { try{ //a reference to this anonymous function var callee = arguments.callee, //and an undefined object that can be used later temporary; //return "null" if null or undefined if (args === null || args === temporary) { return "null"; } if (jQuery.isFunction(args)) { return '"[object Function]"'; } //figure out an object type switch (Object.prototype.toString.call(args)) { case "[object Boolean]": case "[object Number]": return args+""; case "[object Array]": temporary = []; jQuery.each(args, function(){temporary[temporary.length] = callee(this);}); return "[" + temporary.join(",") + "]"; case "[object Object]": temporary = []; jQuery.each(args, function(n,v) { if (jQuery.isFunction(v)) { //helper for methods like isPropagationStopped if (n.substr(0,2) != 'is') { return; } v = v(); } temporary[temporary.length] = callee(n+"") + ":" + callee(v); }); return "{" + temporary.join(",") + "}"; case "[object String]": case "[object Date]": case "[object RegExp]": return '"' + args.toString() .replace(/([\\"\\r\\n\\t\\x00-\\x19])/g,//"-because this messes up my editor highlighting function ($0,$1) { switch($1) { case '\\r': return "\\\\r"; case '\\n': return "\\\\n"; case '\\t': return "\\\\t"; case "'": case '"': return "\\\\" + $1; default: return ""; } }) + '"'; } //is it an element? if (args === window) { return '"window"'; } if (args === document) { return '"document"'; } if (args === document.body) { return '"body"'; } if ("nodeType" in args) { //get a jQuery selector string for the object return (function () { var str = [], element = args, isXML = jQuery.isXMLDoc(args), temp, temp2, i, l; while (element) { //break on an id if (element.id && element.nodeType == 1) { str.unshift("#" + element.id); break; } temp = path(); if (temp) { str.unshift(temp); } temp = null; element = element.parentNode; } return str.length > 0 ? callee(str.join(">")) : args.outerHTML || args.innerHTML || callee(args+""); function path () { var temp; //an attribute returns a string representing its parent if (element.nodeType == 2) { temp = ([ arguments.callee(element.parentNode), "[", element.nodeName, "=", callee(element.value+""), "]" ]).join(""); //increment element element = element.parentNode; } else if (element.nodeType == 1) { temp = element.nodeName || element.tagName || element.localName; if (element.parentNode) { temp2 = element.parentNode.getElementsByTagName(temp); if (temp2.length > 1) { l = temp2.length; temp += ":eq(";//the CSS3 selector - ":nth-of-type("; - xpath [ for (i=0;i<l;++i) { if (temp2[i] === element) { temp += ""+i; break; } } temp += ")"; } } } return temp; } })(); } return '"undefined"'; } catch(e) { console.log(e, args); // Error: Permission denied to access property 'nodeType' from a non-chrome context <div class="anonymous-div"> } return "null"; //arguments should }; EOD ); /* * jQuery natively uses the $ as an alias function * this is a simple identifier for jQuery, but the simplest * function name that PHP will support is _ * but I found at least one extension (gettext) that uses * that function name, so I used __ * * this could potentially cause problems and is not recommended though * * <code> * __('#test') * ->css(array('backgroundColor' => 'red')); * </code> * * @param mixed $selector * @param string $context * * @return jQueryP * / if (!function_exists('__')) { function __ ($selector, $context=null) { return new jQueryP($selector, $context); } } /** * Another option is to create an anonymous function * and assign it to $_ if that name isn't already taken * this could potentially cause problems and is not recommended though * / if (!isset($GLOBALS['_'])) { $GLOBALS['_'] = create_function('$selector, $context=null', 'return new jQueryP($selector, $context);'); //$_('#test')->css('opacity', 0.9); } */ if (isset($_SERVER['HTTP_METHOD'])) { $_SERVER['REQUEST_METHOD'] = $_SERVER['HTTP_METHOD']; } //use output bufferring in order to use callback function appropriately //but only if the HTTP method is being used if ( isset($_SERVER['REQUEST_METHOD']) && //check for the HTTP method $_SERVER['REQUEST_METHOD'] == jQueryP::HTTP_METHOD ) { ob_start(); } ?>