Welcome to Abdul Malik Ikhsan's Blog

Using React.js in Mezzio Application

Posted in Mezzio, React.js, Tutorial PHP by samsonasik on June 25, 2020

Ok, in 3 previous JavaScript posts, I posted how to use Vue.js in Mezzio Application. Now, in this post, I will show you how to use React.js in Mezzio Application.

Let’s start with download the mezzio skeleton:

composer create-project mezzio/mezzio-skeleton mezzio-react

I assume next you choose the following options:

  • Type of Installation: Modular (3)
  • Container: Laminas ServiceManager (3)
  • Router: Laminas Router (3)
  • Template Engine: Laminas View (3)

Now, we are on the same page!

The scenario is same, we want to create an SPA application. In Mezzio part, to make it work, it require template handling for ajax request.

We can create middleware for that:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class XMLHttpRequestTemplateMiddleware implements MiddlewareInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        if (in_array('XMLHttpRequest', $request->getHeader('X-Requested-With'), true)) {
            (function ($template) {
                $template->layout = false;
            })->bindTo($this->template, $this->template)($this->template);
        }

        return $handler->handle($request);
    }
}

In above middleware, we set template layout to false to disable layout when request has X-Requested-With = XmlHttpRequest as an ajax detection. Let’s register above middleware in ConfigProvider class:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Middleware\XMLHttpRequestTemplateMiddleware::class => ReflectionBasedAbstractFactory::class,
            ],
        ];
    }
    // ...
}

and in the pipeline before DispatchMiddleware:

<?php
 // config/pipeline.php
use App\Middleware\XMLHttpRequestTemplateMiddleware;
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->pipe(XMLHttpRequestTemplateMiddleware::class);
    $app->pipe(DispatchMiddleware::class);
    // ...
};

We need to handle 404 Pages that can work in Ajax request, so we can create a new middleware for that, for example: App\Middleware\NotFoundMiddleware:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class NotFoundMiddleware implements MiddlewareInterface
{
    private $template;
    private $config;

    public function __construct(TemplateRendererInterface $template, array $config)
    {
        $this->template = $template;
        $this->config   = $config;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        return new HtmlResponse(
            $this->template->render($this->config['mezzio']['error_handler']['template_404'])
        );
    }
}

The above middleware need to be registered to ConfigProvider:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Middleware\NotFoundMiddleware::class  => ReflectionBasedAbstractFactory::class,
            ],
        ];
    }
    // ...
}

And then, add to config/pipeline after DispatchMiddleware:

<?php
 // config/pipeline.php

use App\Middleware\NotFoundMiddleware;
use App\Middleware\XMLHttpRequestTemplateMiddleware;

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->pipe(XMLHttpRequestTemplateMiddleware::class);
    $app->pipe(DispatchMiddleware::class);
    $app->pipe(NotFoundMiddleware::class);
    // ...
};

Now, let’s add About and Contact page handlers:

1. About Page

<?php
// src/App/src/Handler/AboutPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AboutPageHandler implements RequestHandlerInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse($this->template->render('app::about-page'));
    }
}

With templates:

<!-- src/templates/app/about-page.phtml -->
<h1>About Me</h1>
<p>
    I'm a web developer.
</p>

2. Contact Page

<?php
// src/App/src/Handler/ContactPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ContactPageHandler implements RequestHandlerInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse($this->template->render('app::contact-page'));
    }
}

With templates:

<!-- src/templates/app/contact-page.phtml -->
<h1>Contact Me</h1>
<p>
    You can contact me via <a href="mailto: foo@bar.baz.com">foo@bar.baz.com</a>
</p>

Handlers Registration

Both AbooutPageHandler and ContactPageHandler need to be registered in ConfigProvider class:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Handler\AboutPageHandler::class => ReflectionBasedAbstractFactory::class,
                Handler\ContactPageHandler::class => ReflectionBasedAbstractFactory::class,
                // ...
            ],
        ];
    }
    // ...
}

and in the routes:

<?php
// config/routes.php
return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->get('/about', App\Handler\AboutPageHandler::class, 'about');
    $app->get('/contact', App\Handler\ContactPageHandler::class, 'contact');
};

Now, we have 3 html pages: Home, About, and Contact. It’s enough for demonstration.

JS dependencies

We can register js dependencies in the layout by add the following js:

<?php
// src/App/templates/layout/default.phtml

$this->headLink()
    ->prependStylesheet('https://use.fontawesome.com/releases/v5.12.1/css/all.css')
    ->prependStylesheet('https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css');
$this->inlineScript()

    ->prependFile('/js/app.js', 'module')
    ->prependFile('https://unpkg.com/react-router-dom@5.2.0/umd/react-router-dom.js')
    ->prependFile('https://unpkg.com/html-react-parser@0.13.0/dist/html-react-parser.js')
    ->prependFile('https://unpkg.com/dompurify@2.0.12/dist/purify.js')
    ->prependFile('https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js')
    ->prependFile('https://unpkg.com/react@16.13.1/umd/react.production.min.js')

    ->prependFile('https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js')
    ->prependFile('https://code.jquery.com/jquery-3.5.0.min.js');
?>

React dependencies are react, react-dom, html-react-parser, and react-router-dom. The dompurify will be used to purify the js before displaying. For /js/app.js, that’s our code in public directory to make routing definition. In the layout, we need element for mounting, let’s define is as “root” under body:

<!-- // src/App/templates/layout/default.phtml -->
<body class="app">
    <div id="root">
        
    </div>
    <?=$this->inlineScript()?>
</body>

Now, we can define a Page component creator function, eg: createPage(), we can create a js file for it that will be consumed by /js/app.js under public directory later:

// public/js/create-page.js
let createPage = (title) => class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = { content: ''};
    }

    componentDidMount() {
        new Promise( (resolve) => {
            fetch(
                this.props.location.pathname,
                {
                    method: 'GET',
                    headers: {
                        'X-Requested-With': 'XMLHttpRequest',
                    }
                }
            ).then(response =>  resolve(response.text()));
        }).then(result => {
            this.setState({ content : result });
            document.title = title;
        });
    }

    render() {
      return React.createElement(
            'div',
            {
                className : "app-content",
            },
            React.createElement(
                "main",
                {
                    className : "container"
                },
                HTMLReactParser(DOMPurify.sanitize(this.state.content))
            )
        );
    }
}

export default createPage;

Above, we use location route path page, and change title after content grabbed based on passed title parameter. We use HTMLReactParser to generate html for the raw html from the server response.

Now, let create a Navigation, we can create Navigation component, eg, in /js/Navigation.js under public directory:

// public/js/Navigation.js
const {
    NavLink
} = ReactRouterDOM;

const {
    Component,
    createElement
} = React;

class Navigation extends Component {
    render() {
      return createElement(
            'nav',
            {
                className : "navbar navbar-expand-sm navbar-dark bg-dark fixed-top",
                role: "navigation"
            },
            createElement(
                'div',
                {
                    className: "container"
                },
                createElement(
                    'div',
                    {
                        className: "navbar-header"
                    },
                    createElement(
                        'button',
                        {
                            className: "navbar-toggler",
                            "data-toggle": "collapse",
                            "data-target": "#navbarCollapse",
                            "aria-controls": "#navbarCollapse",
                            "aria-expanded": "false",
                            "aria-label": "Toggle navigation"
                        },
                        createElement(
                            'span',
                            {
                                className: "navbar-toggler-icon"
                            }
                        )
                    ),
                    createElement(
                        NavLink,
                        {
                            to: "/",
                            className: "navbar-brand"
                        },
                        createElement(
                            'img',
                            {
                                src: "https://docs.laminas.dev/img/laminas-mezzio-rgb.svg",
                                alt: "Laminas Mezzio",
                                height: 56
                            }
                        )
                    )
                ),
                createElement(
                    'div',
                    {
                        className: "collapse navbar-collapse",
                        id: "navbarCollapse"
                    },
                    createElement(
                        "ul",
                        {
                            className: "navbar-nav mr-auto"
                        },
                        createElement(
                            "li",
                            {
                                className: "nav-item"
                            },
                            createElement(NavLink, { className: 'nav-link', to: "/", exact: true }, "Home")
                        ),
                        createElement(
                            "li",
                            {
                                className: "nav-item"
                            },
                            createElement(NavLink, { className: 'nav-link', to: "/about", exact: true }, "About")
                        ),
                        createElement(
                            "li",
                            {
                                className: "nav-item"
                            },
                            createElement(NavLink, { className: 'nav-link', to: "/contact", exact: true }, "Contact")
                        )
                    )
                )
            )
        );
    }
}

export default Navigation;

Above, we define the navigation, with add “active” class on link selected.

Finally, our /js/app.js under public directory that consume createPage function and Navigation component to be used for routing definition and navigation.

// public/js/app.js
import createPage from './create-page.js';
import Navigation from './Navigation.js';

const {
    BrowserRouter,
    Switch,
    Route
} = ReactRouterDOM;

const Main = () => React.createElement(
    "main",
    null,
    React.createElement(
        Switch,
        null,
        React.createElement(
            Route, {
                exact: true,
                path: "/",
                component: createPage('Home')
            }
        ),
        React.createElement(
            Route, {
                exact: true,
                path: "/about",
                component: createPage('About')
            }
        ),
        React.createElement(
            Route, {
                exact: true,
                path: "/contact",
                component: createPage('Contact')
            }
        ),
        React.createElement(
            Route, {
                exact: true,
                path: "*",
                component: createPage('404 Page')
            }
        )
    )
);

const Header = () => React.createElement(
    'header',
    {
        className: 'app-header'
    },
    React.createElement(Navigation)
);

const App = () => React.createElement(
    "div",
    null,
    React.createElement(Header, null),
    React.createElement(Main, null)
);

ReactDOM.render(
    React.createElement(
        BrowserRouter,
        null,
        React.createElement(App, null)
    ),
    document.getElementById('root')
);

// https://reactjs.org/docs/react-without-jsx.html
// https://www.pluralsight.com/guides/just-plain-react
// https://codepen.io/pshrmn/pen/YZXZqM?editors=1010

Now, if we check, we will get SPA working:

That’s it! I uploaded the sample source code at github: https://github.com/samsonasik/mezzio-react

References:
https://reactjs.org/docs/react-without-jsx.html
https://www.pluralsight.com/guides/just-plain-react
https://codepen.io/pshrmn/pen/YZXZqM?editors=1010

Tagged with: ,

Using Vuex’s Vue.js and sessionStorage combo for searchable get api data and cached in Mezzio Application

Posted in Javascript, Mezzio, Tutorial PHP, Vue.js by samsonasik on June 13, 2020

So, this is the 3rd post about usage of Vue.js in Mezzio Application. If you haven’t read my previous 2 posts, I suggest you to read them first:

Ok, let’s continue. Now, we are going to use Vuex as state management (when without refresh) and native sessionStorage combo to handle searched data in next refresh to avoid unnecessary re-query data as previously already searched. For note, I use sessionStorage so next close – re-open browser will clear the data.

Load the Vuex Library

We can load Vuex library in the layout:

<?php
// src/App/templates/layout/default.phtml
// ...
    ->prependFile('/js/app.js')
    ->prependFile('https://unpkg.com/vuex@3.4.0/dist/vuex.js')
    ->prependFile('https://unpkg.com/vue-router@3.3.2/dist/vue-router.js')
    ->prependFile('https://unpkg.com/vue@2.6.11/dist/vue.js')
// ...

The Data

For example, we want to display portfolio data via API. For example, we have the following portfolio array data example (in real life, you an use DB ofcourse)

<?php
// data/portfolio.php
return [
    [
        'id'    => 1,
        'title' => 'Website A',
        'image' => 'https://via.placeholder.com/150/FF0000/FFFFFF?text=website%20A',
        'link'  => 'https://www.website-a.com',
    ],
    [
        'id'    => 2,
        'title' => 'Website B',
        'image' => 'https://via.placeholder.com/150/0000FF/808080?text=website%20B',
        'link'  => 'https://www.website-b.com',
    ],
    [
        'id'    => 3,
        'title' => 'Website C',
        'image' => 'https://via.placeholder.com/150/000000/FFFFFF?text=website%20C',
        'link'  => 'https://www.website-c.com',
    ]
];

If you use GIT with mezzio skeleton, the data need to be registered to .gitignore to allow to be committed:

# data/.gitignore
*
!cache
!cache/.gitkeep
!.gitignore
!portfolio.php

The API

Now, time to create API page, for example App\Handler\Api\PortfolioApiHandler:

<?php
// src/App/src/Handler/Api/PortfolioApiHandler.php
declare(strict_types=1);

namespace App\Handler\Api;

use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class PortfolioApiHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $data  = include './data/portfolio.php';
        $keyword = $request->getQueryParams()['keyword'] ?? '';

        if ($keyword) {
            $data = array_filter($data, function ($value) use ($keyword) {
                return (
                    strpos(strtolower($value['title']), strtolower($keyword)) !== false
                    ||
                    strpos(strtolower($value['link']), strtolower($keyword)) !== false
                );
            });
        }

        return new JsonResponse($data);
    }
}

Above, we use array_filter to search portfolio data for title and link with keyword query parameter.

Next, we can register to our ConfigProvider class:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Handler\Api\PortfolioApiHandler::class => ReflectionBasedAbstractFactory::class,
            ],
        ];
    }
    // ...
}

and in the routes:

<?php
// config/routes.php
return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->get('/api/portfolio', App\Handler\Api\PortfolioApiHandler::class, 'api-portfolio');
};

The Page

We need to consume the API via a page, for example, we create handle for it: App\Handler\PortfolioPageHandler:

<?php
// src/App/src/Handler/PortfolioPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class PortfolioPageHandler implements RequestHandlerInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse($this->template->render('app::portfolio-page'));
    }
}

Next, we can register to our ConfigProvider class:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Handler\Api\PortfolioApiHandler::class => ReflectionBasedAbstractFactory::class,
                Handler\PortfolioPageHandler::class    => ReflectionBasedAbstractFactory::class,
            ],
        ];
    }
    // ...
}

and in the routes:

<?php
// config/routes.php
return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->get('/api/portfolio', App\Handler\Api\PortfolioApiHandler::class, 'api-portfolio');
    $app->get('/portfolio', App\Handler\PortfolioPageHandler::class, 'portfolio');
};

The Template

For view, we need to display portfolio data with allow to search by keyword via input text. We can create view as follow:

<!-- src/App/templates/app/portfolio-page.phtml-->
Keyword: <input type="keyword" id="keyword" v-on:input="this.$parent.search" v-on:focus="this.$parent.search"/> <br /><br />

<table class="table">
	<tr>
		<th>Title</th>
		<th>Image</th>
        <th>Link</th>
	</tr>

    <tr v-if="this.$parent.portfolio.length == 0">
        <td colspan="3" class="text-center">No portfolio found.</td>
    </tr>

    <tr v-for="loop in this.$parent.portfolio" :key="loop.id">
        <td>{{ loop.title }}</td>
        <td><img :src="`${ loop.image }`" /></td>
        <td><a v-bind:href="`${ loop.link }`">{{ loop.link }}</a></td>
    </tr>
</table>

<script type="application/javascript">
const store = new Vuex.Store({
    state: {
        portfolio : {}
    },
    mutations: {
        search (state, data) {
            sessionStorage.setItem('search-' + data.keyword, JSON.stringify(data.value));
            state.portfolio[data.keyword] = data.value;
        }
    }
});

document.querySelector('#keyword').focus();
</script>

In Vue.js template, we can fill JavaScript with “application/javascript” script type. Above, I initialize Vuex’s Store instance with definition of portfolio state data that on search mutation, set stringified object data.value into sessionStorage item based on keyword and fill the state.portfolio[data.keyword] with data.value. We will fill data as keyword and value later in the public/js/app.js. On very first page accessed, we set focus to keyword text field that trigger search function we register in public/js/app.js when define portfolio page component. So, we can finally loop the data searched.

The JavaScript

In public/js/app.js, now, we can portfolio component with the following defintion:

const routes = [
    // ... other page definition here ...
    {
        path: '/portfolio',
        component: createPage(
            'portfolio',
            {
                portfolio : []
            },
            {
                search: function (e) {
                    let keyword = e.target.value;

                    if (typeof store.state.portfolio[keyword] !== 'undefined') {
                        this.portfolio = store.state.portfolio[keyword];

                        return;
                    }

                    if (sessionStorage.getItem('search-' + keyword)) {
                        portfolio     = JSON.parse(sessionStorage.getItem('search-' + keyword));
                        store.commit('search', { keyword: keyword, value: portfolio });
                        this.portfolio = portfolio;

                        return;
                    }

                    (async () => {
                        await new Promise( (resolve) => {
                            fetch(
                                '/api/portfolio?keyword=' + keyword,
                                {
                                    method: 'GET',
                                    headers: {
                                        'Accept': 'application/json',
                                    }
                                }
                            ).then(response =>  resolve(response.json()));
                        }).then(result => this.portfolio = result);

                        store.commit('search', { keyword: keyword, value: this.portfolio });
                    })();
                }
            }
        ),
        meta: {
            title: 'My Portfolio'
        }
    }
];

Above, in definition of portfolio component, we define a portfolio data attribute to empty array. On search function (that we know it triggered in template input focus and input event), we have the following flow:

a. get keyword from e.target.value as keyword input value
b. check if Vuex store.state.portfolio[keyword] not undefined, means it already in Vuex stage, then fill portfolio data attribute with it, then return early.
c. check if there is session storage data with item key “search-” + keyword value, means it already in session storage, then fill portfolio data attribute with its parsed to object from json stringified data, then return early.
d. otherwise, use async/await function to fill portfolio data attribute, and then commit to Vuex store.

Last but not least, add link to /portfolio page in the layout:

<div class="collapse navbar-collapse" id="navbarCollapse">
    <!-- other menu here -->

    <li class="nav-item">
        <router-link to="/portfolio" class="nav-link">Portfolio</router-link>
    </li>

</div>

That’s it, now we have fully functional searchable and cached even on refresh, unless browser is closed and re-open.

I published the code at https://github.com/samsonasik/mezzio-vue if you want to give it a try 😉

Refs:

Using Vue.compile() to activate Vue component’s data and method in Mezzio Application

Posted in Javascript, Mezzio, Tutorial PHP, Vue.js by samsonasik on June 6, 2020

In previous post, we already tried create an SPA application with template rendered via Fetch for XHR purpose. What if we want to call data or/and method in template? With v-html, we can’t! The way we can do is make it compiled with Vue.compile(). Let’s check the JS part:

createPage = (name, object = {}, methods = {}) => {
    return Vue.component('page-' + name, {
        data    : () => Object.assign({content: ''}, object),
        methods : methods,
        mounted () {
            (new Promise( (resolve) => {
                fetch(
                    this.$route.path,
                    {
                        method: 'GET',
                        headers: {
                            'X-Requested-With': 'XMLHttpRequest',
                        }
                    }
                ).then(response =>  resolve(response.text()));
            })).then(result => this.content = result);
        },
        render : function (c) {
            if (this.content == '') {
                return;
            }

            return c(Vue.compile('<div>' + this.content + '</div>'));
        }
    });
}

const routes = [
    { path: '/', component: createPage('home'), meta: {
        title: 'Home'
    } },
    { path: '/about', component: createPage(
        'about',
        {
            name: 'Abdul Malik Ikhsan'
        },
        {
            hit: () => alert('This alert already proof that I am a web developer!')
        }
    ), meta: {
        title: 'About Me'
    } },
    { path: '/contact', component: createPage('contact'), meta: {
        title: 'Contact Me'
    } },
    { path: "*", component: createPage('404'), meta: {
        title: '404 Not Found'
    } }
];

const router = new VueRouter({
    routes,
    base: '/',
    mode: 'history',
    linkExactActiveClass: "active"
});

router.afterEach(to => document.title = to.meta.title);

vue = new Vue({
    router
}).$mount('#root');

In above JS, first, I create a createPage function that in 2nd parameter, can pass custom data besides the current content data, and in 3rd parameter, can pass custom methods definition. With content fetched that assigned to content data, finally, we use it in the Vue.compile() on render.

The another special part is in the template part, it requires to use this.$parent to get parent attribute/method. For example, on the ‘about’ page above, we need to get name data, and can call the hit method, we can do like the following:

<!-- src/templates/app/about-page.phtml -->
<h1>About Me</h1>
<p>
    I'm a web developer. My name is {{ this.$parent.name }}. <br />
    <button v-on:click="this.$parent.hit">Click this as a proof</button>
</p>

That’s it, now you can open the about page and can see like the following:

I uploaded the sample source code at github, if you need to see what the diff between my previous post and this, you can check this PR https://github.com/samsonasik/mezzio-vue/pull/1 😉

Refs:
https://vuejs.org/v2/api/#Vue-compile
https://vuejs.org/v2/guide/render-function.html#Functional-Components
https://stackoverflow.com/questions/51548729/vuejs-vue-app-render-method-with-dynamic-template-compiled-is-throwing-some/51552701

Using Vue.js in Mezzio Application

Posted in Javascript, Mezzio, Tutorial PHP, Vue.js by samsonasik on May 30, 2020

So, another JavaScript post! If you read my post at 2015 about Ember.js usage in Zend Framework 2 application, now let’s try with Vue.js, but for Mezzio application.

Let’s start with download the mezzio skeleton:

composer create-project mezzio/mezzio-skeleton mezzio-vue

I assume next you choose the following options:

  • Type of Installation: Modular (3)
  • Container: Laminas ServiceManager (3)
  • Router: Laminas Router (3)
  • Template Engine: Laminas View (3)

Now, we are on the same page!

The scenario is same, we want to create an SPA application. In Mezzio part, to make it work, it require template handling for ajax request.

We can create middleware for that:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class XMLHttpRequestTemplateMiddleware implements MiddlewareInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        if (in_array('XMLHttpRequest', $request->getHeader('X-Requested-With'), true)) {
            (function ($template) {
                $template->layout = false;
            })->bindTo($this->template, $this->template)($this->template);
        }

        return $handler->handle($request);
    }
}

In above middleware, we set template layout to false to disable layout when request has X-Requested-With = XmlHttpRequest as an ajax detection. Let’s register above middleware in ConfigProvider class:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Middleware\XMLHttpRequestTemplateMiddleware::class => ReflectionBasedAbstractFactory::class,
            ],
        ];
    }
    // ...
}

and in the pipeline before DispatchMiddleware:

<?php
 // config/pipeline.php
use App\Middleware\XMLHttpRequestTemplateMiddleware;
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->pipe(XMLHttpRequestTemplateMiddleware::class);
    $app->pipe(DispatchMiddleware::class);
    // ...
};

We need to handle 404 Pages that can work in Ajax request, so we can create a new middleware for that, for example: App\Middleware\NotFoundMiddleware:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class NotFoundMiddleware implements MiddlewareInterface
{
    private $template;
    private $config;

    public function __construct(TemplateRendererInterface $template, array $config)
    {
        $this->template = $template;
        $this->config   = $config;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        return new HtmlResponse(
            $this->template->render($this->config['mezzio']['error_handler']['template_404'])
        );
    }
}

The above middleware need to be registered to ConfigProvider:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Middleware\NotFoundMiddleware::class  => ReflectionBasedAbstractFactory::class,
            ],
        ];
    }
    // ...
}

And then, add to config/pipeline after DispatchMiddleware:

<?php
 // config/pipeline.php

use App\Middleware\NotFoundMiddleware;
use App\Middleware\XMLHttpRequestTemplateMiddleware;

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->pipe(XMLHttpRequestTemplateMiddleware::class);
    $app->pipe(DispatchMiddleware::class);
    $app->pipe(NotFoundMiddleware::class);
    // ...
};

Now, let’s add About and Contact page handlers:

1. About Page

<?php
// src/App/src/Handler/AboutPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AboutPageHandler implements RequestHandlerInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse($this->template->render('app::about-page'));
    }
}

With templates:

<!-- src/templates/app/about-page.phtml -->
<h1>About Me</h1>
<p>
    I'm a web developer.
</p>

2. Contact Page

<?php
// src/App/src/Handler/ContactPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ContactPageHandler implements RequestHandlerInterface
{
    private $template;

    public function __construct(TemplateRendererInterface $template)
    {
        $this->template = $template;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse($this->template->render('app::contact-page'));
    }
}

With templates:

<!-- src/templates/app/contact-page.phtml -->
<h1>Contact Me</h1>
<p>
    You can contact me via <a href="mailto: foo@bar.baz.com">foo@bar.baz.com</a>
</p>

Handlers Registration

Both AbooutPageHandler and ContactPageHandler need to be registered in ConfigProvider class:

<?php
 // src/App/src/ConfigProvider.php
declare(strict_types=1);

namespace App;

use Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory;

class ConfigProvider
{
    // ...
    public function getDependencies() : array
    {
        return [
            'invokables' => [
                 // ...
            ],
            'factories'  => [
                // ..
                Handler\AboutPageHandler::class => ReflectionBasedAbstractFactory::class,
                Handler\ContactPageHandler::class => ReflectionBasedAbstractFactory::class,
                // ...
            ],
        ];
    }
    // ...
}

and in the routes:

<?php
// config/routes.php
return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->get('/about', App\Handler\AboutPageHandler::class, 'about');
    $app->get('/contact', App\Handler\ContactPageHandler::class, 'contact');
};

Now, we have 3 html pages: Home, About, and Contact. It’s enough for demonstration.

JS dependencies

We can register js dependencies in the layout by add the following js:

<?php
// src/App/templates/layout/default.phtml

$this->headLink()
    ->prependStylesheet('https://use.fontawesome.com/releases/v5.12.1/css/all.css')
    ->prependStylesheet('https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css');
$this->inlineScript()

    ->prependFile('/js/app.js')
    ->prependFile('https://unpkg.com/vue-router@3.3.2/dist/vue-router.js')
    ->prependFile('https://unpkg.com/vue@2.6.11/dist/vue.js')

    ->prependFile('https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js')
    ->prependFile('https://code.jquery.com/jquery-3.5.0.min.js');
?>

Vue dependencies are Vue.js core and Vue Router for routing. For /js/app.js, that’s our code to make routing definition. In the layout, we need element for mounting, let’s define is as “root” under body:

<!-- // src/App/templates/layout/default.phtml -->
<body class="app">
    <div id="root">
        
    </div>
    <?=$this->inlineScript()?>
</body>

Now, we can define the router links inside “root” div:

<!-- // src/App/templates/layout/default.phtml -->
<div id="root">

        <header class="app-header">
            <nav class="navbar navbar-expand-sm navbar-dark bg-dark fixed-top" role="navigation">
                <div class="container">
                    <div class="navbar-header">
                        <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="#navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
                            <span class="navbar-toggler-icon"></span>
                        </button>
                        <router-link to="/" class="navbar-brand"><img src="https://docs.laminas.dev/img/laminas-mezzio-rgb.svg" alt="Laminas Mezzio" height="56" /></router-link>
                    </div>
                    <div class="collapse navbar-collapse" id="navbarCollapse">
                        <ul class="navbar-nav mr-auto">
                            <li class="nav-item">
                                <router-link to="/" class="nav-link">Home</router-link>
                            </li>
                            <li class="nav-item">
                                <router-link to="/about" class="nav-link">About</router-link>
                            </li>
                            <li class="nav-item">
                                <router-link to="/contact" class="nav-link">Contact</router-link>
                            </li>
                        </ul>
                    </div>
                </div>
            </nav>
        </header>

</div>

Next, time for the view content placeholder, we can define router-view inside “root” div as well for place to collect the content:

<!-- // src/App/templates/layout/default.phtml -->
<div id="root">
        <header class="app-header"> <!-- content app header before --> </header>

        <main class="container">
            <div id="app">
                <keep-alive> <!-- make content cached if already loaded -->
                    <router-view></router-view>
                </keep-alive>
            </div>
        </main>

        <!-- footer here -->
</div>

Now, our JS file in public/js/app.js for routing definition:

createPage = (name) => {
    return Vue.component('page-' + name, {
        data: () => {
            return  {
              content: 'Loading...'
            }
        },
        mounted () {
            (new Promise( (resolve) => {
                fetch(
                    this.$route.path,
                    {
                        method: 'GET',
                        headers: {
                            'X-Requested-With': 'XMLHttpRequest',
                        }
                    }
                ).then(response =>  resolve(response.text()));
            })).then(result => this.content = result);
        },
        template: '<div v-html="content"></div>'
    });
}

const routes = [
    { path: '/', component: createPage('home') },
    { path: '/about', component: createPage('about') },
    { path: '/contact', component: createPage('contact') },
    { path: '*', component: createPage('404') }
];

const router = new VueRouter({
    routes,
    base: '/',
    mode: 'history',
    linkExactActiveClass: "active"
});
const app    = new Vue({router}).$mount('#root')

In above code, we create a page component on the fly via function createPage and apply to each path. Setup VueRouter with routes definition, and make a Vue instance with it, mount to div id #root.

Bonus

How about make page title changed after on change page? We can set meta title in each route definition, and use router.afterEach() to apply it:

// ...
const routes = [
	{ path: '/', component: createPage('home'),  meta: {
            title: 'Home'
        } },
	{ path: '/about', component: createPage('about'), meta: {
            title: 'About Me'
        } },
	{ path: '/contact', component: createPage('contact'), meta: {
            title: 'Contact Me'
        } },
    { path: '*', component: createPage('404'), meta: {
            title: '404 Not Found'
        } }
];

const router = new VueRouter({
    routes,
    base: '/',
    mode: 'history',
    linkExactActiveClass: "active"
});

router.afterEach(to => document.title = to.meta.title);
// ...

Now, if we check, we will get SPA working:

That’s it! I uploaded the sample source code at github: https://github.com/samsonasik/mezzio-vue

References:
https://vuejs.org/v2/guide/
https://router.vuejs.org/guide/#html
https://medium.com/badr-interactive/mengenal-lifecycle-hooks-pada-vue-js-78cd2225a69
https://forum.vuejs.org/t/setting-a-correct-base-url-with-vue-router/24726/2
https://forum.vuejs.org/t/how-do-i-make-an-html-tag-inside-a-data-string-render-as-an-html-tag/13074/3

Handle Promise.allSettled() resolved and each rejected Promise

Posted in Javascript by samsonasik on April 25, 2020

This is a Javascript post! As you probably already knew, that Promise.all() is all or never. How about run all the things even there is/are a rejected promise(s)? There is Promise.allSettled() for that.

The issue is about catching the error for specific promise(s). You CANNOT do this:

await Promise.allSettled(
        [
            promise2,
            promise1,
            promise3
        ]
    )
    .then((results) => results.forEach((result) => {
        // ...
    }))
    .catch(
        // ...
    )
    .finally(
        // ...
    );

As above will only works for Promise.all(), while the resolved promises won’t executed even only there is one promise that rejected.

What you need to do is loop the results, and you will get the result like the following:

On loop, you can check if it has status == ‘rejected’, that a way you know that the promise rejected. The result will always has ‘reason’ key when it rejected, even if the reject value is undefined. So, the code can be:

await Promise.allSettled(
        [
            promise2,
            promise1,
            promise3
        ]
    )   
    .then(
        (results) => {
            for (let result of results) {
                if (result.status == 'rejected') {
                    // handle rejected promise here
                    // eg: make console.log()...
                    console.log('error : ' + result.reason);

                    // continue to next iteration
                    continue;
                }

                // handle fulfilled promise here
                // eg: append in the div
                document.getElementsByClassName('load')[0].innerHTML += result.value + '<br />';
            }
        }
    )
    .finally(
        // ...
    );

That’s it!

References:

Tagged with: ,

Using Ember.js in Zend Framework 2 Application

Posted in Javascript, Tutorial PHP, Zend Framework 2 by samsonasik on January 7, 2015

Ember.js is one of the javascript frameworks that adopt Single-Page Application principles. When working with Zend Framework 2 application, we can terminate 2 step view process to just render the view (not the layout) when the request that comes is XmlHttpRequest. We can do it in our Module.php like the following code :

use Zend\View\Model\ViewModel;

class Module
{
    public function onBootstrap($e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $sharedEvents = $eventManager->getSharedManager();
        $sharedEvents->attach('Zend\Mvc\Controller\AbstractActionController',
            'dispatch', function($e)
        {
            $result = $e->getResult();
            if ($result instanceof ViewModel) {
                $result->setTerminal($e->getRequest()->isXmlHttpRequest());
            }
        });

        // ...
    }
    
    // ...
}

At this post, I will try to make a 3 static page : ‘Home’, ‘About’, and ‘Contact’ in Application module. Let’s first create the navigation :

// config/autoload/navigation.global.php
return [
    'navigation' => [
        'default' => [
            [
                'label' => 'Home',
                'route' => 'home'
            ],
            [
                'label'  => 'About',
                'route'  => 'about',
            ],
            [
                'label' => 'Contact',
                'route' => 'contact',
            ],
        ],
    ],
];

We can then register the navigation service :

// module/Application/config/module.config.php
// ...
   'service_manager' => [
      'factories' => [
            'Navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
       ],
    ]
// ...

From the navigation registered above, we can create 3 controller like this :
1. IndexController

// module/Application/src/Application/Controller/IndexController.php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel();
    }
}

2. AboutController

// module/Application/src/Application/Controller/AboutController.php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class AboutController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel();
    }
}

3. ContactController

// module/Application/src/Application/Controller/ContactController.php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class ContactController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel();
    }
}

Controllers created, we can register it into module.config.php under ‘router’ and ‘controllers’ :

// module/Application/config/module.config.php
// ...
   'router' => [
        'routes' => [
            'about' => [
                'type'    => 'Literal',
                'options' => [
                    'route'    => '/about',
                    'defaults' => [
                        'controller' => 'Application\Controller\About',
                        'action'        => 'index',
                    ],
                ],
            ],

            'contact' => [
                'type'    => 'Literal',
                'options' => [
                    'route'    => '/contact',
                    'defaults' => [
                        'controller' => 'Application\Controller\Contact',
                        'action'        => 'index',
                    ],
                ],
            ],
         ],
    ],

    'controllers' => [
        'invokables' => [
            'Application\Controller\Index' => 'Application\Controller\IndexController',
            'Application\Controller\Contact' => 'Application\Controller\ContactController',
            'Application\Controller\About' => 'Application\Controller\AboutController',
        ],
    ],
// ...

I assume you have using default ZendSkeletonApplication so the IndexController route already defined. Ok, now we need to fill the view.
1 index view ( same as the skeleton app view/application/index/index.phtml )
2 about view

// module/Application/view/application/about/index.phtml
<h1>About Me</h1>
<p>
    I'm a web developer.
</p>

3 contact view

// module/Application/view/application/contact/index.phtml
<h1>Contact Me</h1>
<p>
    You can contact me via <a href="mailto: foo@bar.baz.com">foo@bar.baz.com</a>
</p>

Yup, Let’s go to javascript side.

First, require Ember.js in bower.json and install it :

// bower.json
{
    "name":"ZF2 App with Ember Demo",
    "dependencies": {
        "ember": "1.*"
    }
}

Configure it to be installed in public/js folder in .bowerrc.

// .bowerrc
{
    "directory": "public/js",
    "json": "bower.json"
}

Run bower install :

bower install

For Ember 1.10, As we will need template compilation, We need to require ember-template-compiler like shown in here. To make it included, we need to require it in layout.phtml in headScript() view helper :

echo $this->headScript()
            ->prependFile($this->basePath() . '/js/bootstrap.min.js')
            ->prependFile($this->basePath() . '/js/jquery.min.js')
            ->prependFile($this->basePath() . '/js/respond.min.js', 'text/javascript', array('conditional' => 'lt IE 9',))
            ->prependFile($this->basePath() . '/js/html5shiv.js',   'text/javascript', array('conditional' => 'lt IE 9',))

            // ember js dependencies
            ->appendFile($this->basePath() . '/js/ember/ember-template-compiler.js')
            ->appendFile($this->basePath() . '/js/ember/ember.min.js');
        ; 

Now, we need to create a new javascript file for its application specific requirement, I name it app.js :

// public/js/app.js
App = Ember.Application.create();

App.Router.map(function() {
    this.resource('home', {
        path: '/'
    });
    this.resource('about');
    this.resource('contact');
});

Layout

We need to replace :

<?php echo $this->content; ?>

with

{{outlet}}

And wrap it in <script type="text/x-handlebars"> :

// module/Application/view/layout/layout.phtml
// ...
<script type="text/x-handlebars">
        <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
            <div class="container">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="<?php echo $this->url('home') ?>"><img src="<?php echo $this->basePath('img/zf2-logo.png') ?>" alt="Zend Framework 2"/> <?php echo $this->translate('Skeleton Application') ?></a>
                </div>
                
                    <div class="collapse navbar-collapse">
                        <ul class="nav navbar-nav">
                            <?php $navigationContainer = $this->navigation('navigation')->getContainer();
                                  foreach($navigationContainer as $page) {
                            ?>
                                <li>
                                    {{#link-to '<?php echo $page->get('route'); ?>'}}
                                        <?php echo $page->get('label'); ?>
                                    {{/link-to}}
                                </li>  
                            <?php   }   ?>
                        </ul>
                        
                    </div><!--/.nav-collapse -->
            </div>
        </nav>
        
        <div class="container">
            {{outlet}}
        </div>
</script>

We need to require our public/js/app.js in footer :

<?php
$script =  $this->inlineScript();
$script->appendFile($this->basePath() . '/js/app.js');

echo $script;
?>
</body> // means in the footer!

Yup, time to make ajax works, modify public/js/app.js by adding the following codes :

// public/js/app.js
// ...
App.HomeRoute = Ember.Route.extend({
   beforeModel: function() {
     return $.ajax({
        url: '/'
     })
     .then(function(response) {
        Ember.TEMPLATES.home = Ember.Handlebars.compile(response);
    });
   }
});

App.AboutRoute = Ember.Route.extend({
   beforeModel: function() {
     return $.ajax({
        url: '/about'
     })
     .then(function(response) {
        Ember.TEMPLATES.about = Ember.Handlebars.compile(response);
    });
   }
});

App.ContactRoute = Ember.Route.extend({
   beforeModel: function() {
     return $.ajax({
        url: '/contact'
     })
     .then(function(response) {
        Ember.TEMPLATES.contact = Ember.Handlebars.compile(response);
    });
   }
});

// to remove # ( hash ) in url
if (window.history && window.history.pushState) {
    App.Router.reopen({
      location: 'history'
    });
}

Great! If everything ok, then your ZF2 app with Ember.js should work like a magic! The requested page loaded without refreshing the page!. about-png

Bonus

You can make ‘li’ under ‘ul’ for navigation class setted to active when it in the page, with create new ‘li’ component, we can add it in public/js/app.js

// public/js/app.js
// ...
App.LinkLiComponent = Ember.Component.extend({
  tagName: 'li',
  classNameBindings: ['active'],
  active: function() {
    return this.get('childViews').anyBy('active');
  }.property('childViews.@each.active')
});

Ember.Handlebars.helper('link-li', App.LinkLiComponent);

And then, we can modify the looks like :

// module/Application/view/layout/layout.phtml
// ...
<?php $navigationContainer = $this->navigation('navigation')->getContainer();
      foreach($navigationContainer as $page) { ?>

     {{#link-li}}
        {{#link-to '<?php echo $page->get('route'); ?>'}}
           <?php echo $page->get('label'); ?>
        {{/link-to}}
     {{/link-li}}  

<?php   }   ?>

Ok, I hope it useful for you ;). Want to grab the sourcecode ? Clone from my repository https://github.com/samsonasik/zfember 😉

Images :
1. http://www.gravatar.com/avatar/0cf15665a9146ba852bf042b0652780a?s=200
References :
1. http://emberjs.com/
2. http://code.tutsplus.com/tutorials/getting-into-ember-js-part-2–net-31132
3. http://code.tutsplus.com/tutorials/getting-into-emberjs-part-3–net-31394
4. http://stackoverflow.com/questions/17792280/ember-live-uploading-templates
5. http://stackoverflow.com/questions/19871265/ember-js-with-external-handlebars-template
6. http://stackoverflow.com/questions/14328295/how-do-i-bind-to-the-active-class-of-a-link-using-the-new-ember-router
7. http://en.wikipedia.org/wiki/Single-page_application
8. http://emberjs.com/blog/2015/02/07/ember-1-10-0-released.html

Using PHP Phantomjs with Codeception

Posted in Javascript, testing by samsonasik on December 18, 2014

phantomjs logoWhen you do a web acceptance test for web with javascript support, and you want to use phantomjs but doesn’t have root access to install new application, you probably need this, the “jonnyw/php-phantomjs” lib. You can install via composer parallel with “codeception/codeception” by configuring composer.json like the following :
 

{
    "require": {
        "codeception/codeception": "2.*",
        "jonnyw/php-phantomjs": "3.*"
    },
    "scripts": {
        "post-install-cmd": [
            "PhantomInstaller\\Installer::installPhantomJS"
        ],
        "post-update-cmd": [
            "PhantomInstaller\\Installer::installPhantomJS"
        ]
    }
}

and then run :

composer install

Ok, when everything installed, you already have : vendor/bin/phantomjs executable file.

If you add :

   "config": {
        "bin-dir": "bin"
    }

Inside your composer.json, you should have individual bin folder outside the vendor folder so you can run from it like described at http://jonnnnyw.github.io/php-phantomjs/. I personally like it should be inside the vendor instead. You can choose what you prefer.

Next step is start the phantomjs service by run command :

vendor/bin/phantomjs --webdriver=4444

Let’s continue to the sample page, try create a sample html page :

<html>
<body>

   <h1>Hello World!</h1>
 
  <button name="test-btn" id="test-btn">test-btn</button>
  <script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript">
    $(document).ready(function() {
       $("#test-btn").click(function() {
            $("h1").html("abcdef");
       });
    });
  </script> 

</body>
</html>

At this sample page, we want to :

if user go to /index.html, he will see “Hello World!” and don’t see the “abcdef” inside “h1” and then if user click “#test-btn” then he can see “Hello World!” replaced with “abcdef” and see the “abcdef” inside “h1”.

Now, let’s initialize the codecept by open separate console window and run :

vendor/bin/codecept bootstrap

Then, call command :

vendor/bin/codecept generate:cept acceptance Index

We then will get pre-filled configuration and make tests with folder named “tests”. What we need to do, is configure the ‘tests/acceptance.suite.yml’ file like the following :

# Codeception Test Suite Configuration
# filename tests/acceptance.suite.yml
class_name: AcceptanceTester
modules:
    enabled: [WebDriver]
    config:
        WebDriver:
            url: 'http://localhost/codeceptiontest/' # our url base
            browser: firefox
            capabilities:
                unexpectedAlertBehaviour: 'accept'

Great! then we can create a test in ‘tests/acceptance/IndexCept.php’ :

// filename : tests/acceptance/IndexCept.php
$I = new AcceptanceTester($scenario);
$I->wantTo('see home page');
$I->amOnPage('/index.html');
$I->see('Hello World!');
$I->dontSee("abcdef", '//body/h1');
$I->click('#test-btn');
// $I->wait(1); // you can set delay
$I->See('abcdef','//body/h1');

And finally! Run the test!

vendor/bin/codecept run acceptance

run-test-codeception-php-phantomjs

Done 😉

References :
1. http://jonnnnyw.github.io/php-phantomjs/
2. http://codeception.com/docs/modules/WebDriver

Tagged with:

Re-fill selectize js value

Posted in Javascript, Teknologi by samsonasik on November 28, 2014

It’s been a while since I didn’t write a post about non-framework category. Ok, this time, I will show you how to use selectize js on re-fill functionality. Selectize js is a jQuery plugin that useful for tagging and autocomplete. I used it in several projects. Once it’s installed and selectize() called, your form can be like the following :
selectize-1
In images demo above, I want to re-set the “district” based on the “province” changes by javascript. To make selectize still applied to “district”, you need to do :

  • re-set Html option values
  • re-set selectize value options

Ok, let’s do a demo application for this.
1. Preparation
1.a make bower.json for dependency requirements definition

{
    "name":"Selectize Demo",
    "dependencies": {
        "jquery": "1.11.1",
        "selectize":"0.11.2"
    }
}

1.b make .bowerrc for specification

{
    "directory": "js",
    "json": "bower.json"
}

1.c install dependencies

bower install

2. Initialize selectize
We can initialize selectize js by include it in the header ( js and css ) like this :

    <link href="./js/selectize/dist/css/selectize.default.css" media="screen" rel="stylesheet" type="text/css">

    <script type="text/javascript" src="./js/jquery/dist/jquery.min.js"></script>
    <script type="text/javascript" src="./js/selectize/dist/js/standalone/selectize.min.js"></script>

and then, we create the elements which we want to selectize :

<form method="post">

     <select name="province_id" id="province_id">
            <option value="0">--Select Province--</option>
            <option value="1">Jawa Barat</option>
            <option value="2">Jawa Tengah</option>
      </select>

      <select name="district" id="district">
            <option value="0">--Select District--</option>
      </select>

</form>

Now, time to execute :


        $(document).ready(function() {
            //initialize selectize for both fields
            $("#province_id").selectize();
            $("#district").selectize();
        });  

3. Do the awesome
Ok, now what ? We need to re-fill the “district” data on change of “province”, In this case, I wrote a case when using Ajax request and catched by PHP script. So, create a “change-data.php” file :

<?php

if (isset($_POST['province_id'])) {

    $data = [];
    if ($_POST['province_id'] == 1) {
        $data = [
            0 => [
                'id' => 1,
                'name' => 'Bandung',
            ],
            1 => [
                'id' => 2,
                'name' => 'Cimahi',
            ]
        ];
    }

    if ($_POST['province_id'] == 2) {
        $data = [
            0 => [
                'id' => 3,
                'name' => 'Kudus',
            ],
            1 => [
                'id' => 4,
                'name' => 'Cirebon',
            ]
        ];
    }

    echo json_encode($data);
}

Basically, the selectize can be filled by json object that have “text” and “value” key, like the following :

[
    {text: "Bandung", value: 1 },
    {text: "Cimahi", value: 2 }
]

So, we need to get the data, and convert to json object, we can do with eval :

new_value_options = eval('(' + new_value_options + ')');

Ok, now, let’s do this :

$(document).ready(function() {
            //initialize selectize for both fields
            $("#province_id").selectize();
            $("#district").selectize();

            // onchange
            $("#province_id").change(function() {
                $.post('./change-data', { 'province_id' : $(this).val() } , function(jsondata) {
                    var htmldata = '';
                    var new_value_options   = '[';
                    for (var key in jsondata) {
                        htmldata += '<option value="'+jsondata[key].id+'">'+jsondata[key].name+'</option>';

                        var keyPlus = parseInt(key) + 1;
                        if (keyPlus == jsondata.length) {
                            new_value_options += '{text: "'+jsondata[key].name+'", value: '+jsondata[key].id+'}';
                        } else {
                            new_value_options += '{text: "'+jsondata[key].name+'", value: '+jsondata[key].id+'},';
                        }
                    }
                    new_value_options   += ']';

                    //convert to json object
                    new_value_options = eval('(' + new_value_options + ')');
                    if (new_value_options[0] != undefined) {
                        // re-fill html select option field 
                        $("#district").html(htmldata);
                        // re-fill/set the selectize values
                        var selectize = $("#district")[0].selectize;

                        selectize.clear();
                        selectize.clearOptions();
                        selectize.renderCache['option'] = {};
                        selectize.renderCache['item'] = {};

                        selectize.addOption(new_value_options);

                        selectize.setValue(new_value_options[0].value);
                    }

                }, 'json');
            });
        });

That’s it, hope it helpful. Want to grab the code ? grab it from https://github.com/samsonasik/selectize-demo

Tagged with: ,

Zend Framework : CSRF ( Cross Site Request Forgery ) Protection

Posted in Javascript, Teknologi, Tutorial PHP, Zend Framework by samsonasik on May 6, 2011

XHR (  XMLHttpRequest ) adalah backbone dari aplikasi web 2.0. Adalah fungsi javascript yang powerfull yang menciptakan HttpRequest. Namun, dari kemudahan yang didapat, ternyata XHR menghadirkan konsekuensi tersendiri, yaitu security hole. XHR, seperti yang telah diatur oleh W3C (  World Wide Web Consortium ), hanya digunakan ketika request itu berasal dari website di mana javascript di muat ( same origin ) untuk menghindari penyalahgunaan dari pihak yang tidak bertanggungjawab.
‘Tekanan’ untuk membolehkan adanya akses cross domain mengingkat dengan adanya konsep mash-ups (sebuah konsep di mana membolehkan data dari beberapa site untuk bisa digabungkan dalam satu single view). Requirement ini bertentangan dengan “same origin policy” ( kebijakan penggunaan request dalam site yang sama)  yang diformulasikan untuk XHR. Sebagai response dari kebutuhan ini, maka kebijakan tersebut  diextends ke “level 2” standard, yang membolehkan akses cross domain jika persyaratan lain terpenuhi, untuk melindungi pengguna (W3C Web Applications Working Group, 2008).
Baca Selengkapnya

CORS ( Cross-Origin Resource Sharing ) : connecting multiple site with multiple request method

Posted in Javascript, Teknologi by samsonasik on April 23, 2011

CORS adalah draft teknologi yang dikembangkan oleh W3C sebagai alternatif yang lebih modern ketimbang JSONP. Ketika JSONP hanya men-support  GET request method, CORS juga mensupport POST request method. Teknologi ini dikembangkan karena semakin kompleksnya kebutuhan mashup data di dunia internet. Ide dasar dari CORS ini adalah meng-custom HTTP Header agar client dan server dapat mengetahui satu sama lain apabila request dan response berhasil atau gagal dilakukan.
Kali ini, saya akan mencoba memaparkan tentang contoh implementasi CORS menggunakan jQuery javascript library, dan plugin CORS. Plugin asli dapat didownload di sini , yang akan saya sampaikan di sini sudah saya modifikasi dari aslinya untuk kebutuhan jQuery versi 1.5.
Pertama, kita siapkan dulu jQuery library kita, bisa didownload di sini. Setelah itu, kita buat plugin untuk handle CORS sebagai berikut :

(function($) {
    //activate cors support <-- for jQuery 1.5
    jQuery.support.cors = true;

    $.corsGET = function(url,callback){
        try{
           jQuery.get(url, callback);
        }catch(e){
            // jQuery get() failed, try IE8 CORS, or use the proxy
            if (jQuery.browser.msie && window.XDomainRequest) {
                // Use Microsoft XDR
                var xdr = new XDomainRequest();
                xdr.open("get", url);
                xdr.onload = function() {
                    callback(this.responseText, 'success');
                };
                xdr.send();
            } else{
                try {
                    // Ancient browser, use our proxy
                    var ancientcallback = function() {
                    var textstatus = 'error';
                    var data = 'error';

                    if ((this.readyState == 4)
                        && (this.status == '200')) {
                            textstatus = 'success';
                            data = this.responseText;
                        }
                        callback(data, textstatus);
                    };

                    // proxy_xmlhttp is a separate script you'll have to set up
                    request = new proxy_xmlhttp();
                    request.open('GET', url, true);
                    request.onreadystatechange = ancientcallback;
                    request.send();
                } catch(e) {
                    // Could not fetch using the proxy
                    alert('failed !!! ');
                }
            }
        }
    };

    $.corsPOST = function(url,data,callback){
        try{
            jQuery.post(url, data, callback);
        }catch(e){
             // jQuery post() failed, try IE8 CORS, or use the proxy
            if (jQuery.browser.msie && window.XDomainRequest) {
                // Use XDR
                var xdr = new XDomainRequest();
                xdr.open("post", url);
                xdr.send(params);
                xdr.onload = function() {
                    callback(xdr.responseText, 'success');
                };
            } else{
                try {
                    // Use the proxy to post the data.
                    request = new proxy_xmlhttp();
                    request.open("POST", url, true);
                    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                    request.send(params);
                } catch(e2) {
                   // could not post using the proxy
                   alert('failed !!! ');
                }
            }
        }
    };

})(jQuery);

Kita bisa simpan plugin ini dan kita beri nama : jquery.cors.js . Ok, setelah itu kita buat contoh file sebagai requester cross domain kita, contoh sebagai berikut :

<!DOCTYPE html>
<html>
<head>
  <title>
      Test CORS ( Cross-Origin Resource Sharing )
  </title>
  <script type="text/javascript" src="jquery-1.5.2.min.js"></script>
  <script type="text/javascript" src="jquery.cors.js"></script>
  <script type="text/javascript">
     $(document).ready(function(){
         $("#sendDataBtn").click(function(){
            //clear label ...
            $("#resultcors").html();

            if ($("#typeofparameter").val()=='get'){
              $.corsGET('http://service.yourdomain.com?nama='+$("input[name=nama]").val()+'&'+(new Date()).getTime() ,
                function(data){
                  $("#resultcors").html('The response is : '+data);
              });
            }else{
              $.corsPOST('http://service.yourdomain.com?'+(new Date()).getTime() , {  nama: $("input[name=nama]").val() },
                function(data){
                  $("#resultcors").html('The response is : '+data);
              });
            }
         });
     });
  </script>
</head>
<body>
    <input type="text" name="nama" size="50" /> <br />
    type : <select name="typeofparameter" id="typeofparameter">
               <option value="get">GET</option>
               <option value="post">POST</option>
           </select>

    <input type="button" id="sendDataBtn" value="Send Data" />

    <br />
    <label id="resultcors"></label>

</body>
</html>

Kalau sudah, kita buat deh file responder-nya ( kita buat di server lain atau atas nama domain lain).

<?php
// Specify domains from which requests are allowed
header('Access-Control-Allow-Origin: *');
// Specify which request methods are allowed
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// Additional headers which may be sent along with the CORS request
// The X-Requested-With header allows jQuery requests to go through
header('Access-Control-Allow-Headers: X-Requested-With');
// Set the age to 1 day ( 86400 ) to improve speed/caching.
header('Access-Control-Max-Age: 86400');

// Exit early so the page isn't fully loaded for options requests
if (strtolower($_SERVER['REQUEST_METHOD']) == 'options') {
    exit();
}

// If raw post data, this could be from IE8 XDomainRequest
// Only use this if you want to populate $_POST in all instances
if (isset($HTTP_RAW_POST_DATA)) {
    $data = explode('&', $HTTP_RAW_POST_DATA);
    foreach ($data as $val) {
       if (!empty($val)) {
         list($key, $value) = explode('=', $val);
         $_POST[$key] = urldecode($value);
       }
    }
}

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    echo  '<strong>posted</strong> : '.strtoupper(  $_REQUEST['nama'] );
}

if ($_SERVER['REQUEST_METHOD'] == 'GET') {
    echo '<strong>getted </strong> : '.strtoupper(  $_GET['nama'] );
}

Taraaaa….Sehingga jika ditest, akan tampil sebagai berikut :

Gambar :
http://t2.gstatic.com/images?q=tbn:ANd9GcSbW9iPimNholkyrzJ2tvD-uqRQ8_5KkBC4hVLys9XEVh4KTN81

Referensi :
http://plugins.jquery.com/project/cors
http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing
http://en.wikipedia.org/wiki/JSONP
http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/
http://enable-cors.org/
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
https://developer.mozilla.org/en/http_access_control
http://blueonionsoftware.com/blog.aspx?p=03aff202-4198-4606-b9d6-686fd13697ee
http://msdn.microsoft.com/en-us/library/cc288060%28v=vs.85%29.aspx

Zend Framework : Output Escaping – preventing XSS

Posted in Javascript, Teknologi, Tutorial PHP, Zend Framework by samsonasik on April 19, 2011

Cross-site scripting (XSS) merupakan salah satu jenis serangan injeksi kode yang secara umum ditemukan di aplikasi berbasis web melalui client-side script.  Salah satu cara menanganinya adalah dengan cara meng-escape output yang tampil pada halaman web. Pada Symfony Framework, output escaping otomatis dilakukan oleh framework itu sendiri, untuk Zend Framework , preventing XSS adalah tugas programmer :). XSS terjadi di bagian View, maka dari itu, Zend Framework menyediakan component Zend_View ( “view” portion of the ZF model-view-controller pattern) yang bisa digunakan untuk meng-escape output dari misalnya :

<script type="text/javascript">alert('hello');</script>

menjadi :

&lt;script type="text/javascript"&gt;alert('hello');&lt;/script&gt;

Sehingga kode javascript tidak tereksekusi. Nah, ada 2 cara penggunaan metode ini :
1. Encoding before echoing
Di layer View, tinggal kita panggil :

<?php echo $this->escape("<script type=\"text/javascript\">alert('hello');</script>"); ?>

2. Encoding when assigning template variables
Cara yang kedua ini, kita escape dulu di controller, contoh sebagai berikut :

$this->view->title = $this->view->escape("<script type=\"text/javascript\">alert('hello');</script>");

Baru di layer view :

 echo $this->title; 

Referensi :
http://www.scribd.com/doc/18171526/Secure-Programming-with-the-Zend-Framework
http://www.minte9.com/kb/zend-view-helper-programming-zend-framework-i329
http://devzone.zend.com/article/3412
http://en.wikipedia.org/wiki/Client-side_script
http://en.wikipedia.org/wiki/Cross-site_scripting
http://id.wikipedia.org/wiki/XSS
http://stackoverflow.com/questions/507593/what-is-the-best-way-to-escape-user-output-with-the-zend-framework

YQL AND JSON – Cross Domain Request Handling

Posted in Javascript, Teknologi by samsonasik on March 17, 2011
YQL Logo

YQL

YQL ( Yahoo! Query Language ) adalah Bahasa Query yang dibuat oleh Yahoo! ( sebagai bagian dari Yahoo! Developer Network ).  YQL memungkinkan kita untuk mengquery, filter, dan mengkombinasikan data dari sumber-sumber yang berbeda di internet. Statement YQL berupa sintaks yang mirip SQL yang memudahkan developer untuk mempelajarinya.

Untuk mengakses YQL Web Service, aplikasi web kita dapat memanggil HTTP GET, passing YQL statement di URL Parameter seperti contoh berikut :

http://query.yahooapis.com/v1/public/yql?q=SELECT * FROM flickr.photos.search WHERE text="Cat"

Baik, saya akan mencoba memberi sebuah contoh simple penggunaan YQL dengan Ajax request ( menggunakan jQuery javascript library ). Anggaplah kita mempunyai server side application berupa sebuah file php dengan content sebagai berikut  ( dengan domain http://subdomain.yourdomain.com dan file yang diakses adalah file.php ) :

<?php
$data = array('nama' =>
 array('aku','kamu','dia','anda','mereka','saya','SAMSONASIK'));

echo json_encode($data);

Nah, kita akan coba akses dari client secara Ajax, dengan kode sebagai berikut :

<html>
 <head>
 <script type="text/javascript" src="jquery.js"></script>
 <script type="text/javascript">

 $(document).ready(function(){
 $("#crosscaller").click(function(){
 //get current time content ...
 var site = 'http://subdomain.yourdomain.com/file.php?'+(new Date()).getTime();
 //yql ...
 var yql = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('select * from html where url="' + site + '"') + '&format=xml&diagnostics=false&_maxage=1';

 // Request that YSQL string, and run a callback function.
 $.ajax({url: yql,
 dataType: 'json',
 jsonp: 'callback',
 jsonpCallback: 'callhandler'
 });
 });
 });

 function callhandler(data) {
 // If we have something to work with...
 if ( data.results[0] ) {
 // Strip out all script tags, for security reasons.
 // BE VERY CAREFUL. This helps, but we should do more.
 data = data.results[0].replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');

 $("#result").html('').html(data);

 var jsondataHTML  = $("#result p").html() ;

 var ItemsJson = JSON.parse(jsondataHTML);

 //get items of json object ...
 alert(ItemsJson.nama[1]);

 }
 // Else, Maybe we requested a site that doesn't exist, and nothing returned.
 else {throw new Error('Failed ...');}
 }

 </script>
 </head>
<body>
 <input type="button"  id="crosscaller" value="get your domain content" />

 <!-- get data -->
 <div id="result"></div>

</body>
</html>

Sehingga jika kita run, akan tampil sebagai berikut :

Image :
http://l.yimg.com/a/i/us/pps/yql128.gif
Referensi :
http://developer.yahoo.com/yql/guide/running-chapt.html
http://icant.co.uk/articles/crossdomain-ajax-with-jquery/
https://gist.github.com/raw/726079/a67c59253fa9700e1d29a5af100b9b2a937f0ad0/yql-cross-domain.html

jQuery – membuat plugin sendiri

Posted in Javascript by samsonasik on November 2, 2010

Selain Prototype Javascript library,  jQuery adalah salah satu library javascript yang banyak dipakai para developer ( kalau saya sih, tukang nonton kamen rider aja, hehehehe).  jQuery mempunyai ukuran yang relative kecil dibandingkan dengan library-library javascript lainnya dan mudah untuk dipelajari. Kita bisa dengan mudah meng-extends core library jQuery, dan membuat plugin sendiri untuk kebutuhan kita yg lebih spesifik.
Pada kesempatan kali ini, saya akan mencoba memaparkan cara membuat plugin sendiri di jQuery.
Hal pertama yg harus kita lakukan adalah menambahkan property ke dalam jQuery.fn object (  anggaplah kita akan membuat sebuah plugin untuk animasi menggoyang element ),  simpan dalam file goyang.jQuery.js

 $.fn.goyang = function(){};

Jika kita ingin menambahkan parameter, kita bisa gunakan seperti berikut :

 $.fn.goyang = function(options){};

Berikut contoh coding  jquery plugin untuk menggoyang element :

$.fn.goyang = function(options){
//setting default value jika parameter tidak dilewatkan
var    defaults = {
 left:'500',
 right:'1000'
 },
 settings = $.extend({}, defaults, options);

 var element = this;  //"this" adalah DOM object
 var leftpost = parseInt($(element).css("left")) -  parseInt(settings.left);
 $(element).click(function(){
 //geser kiri...
 $(element).animate({
 left:  leftpost
 },function(){
 //geser kanan...
 $(element).animate({ left: leftpost + parseInt(settings.right) });
 });
 });
};

Nah, kalau sudah, kita tinggal panggil deh :

<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript" src="goyang.jQuery.js"></script>
<script type="text/javascript">
 $(document).ready(function(){
$('#book').goyang({   /* override default value */     left:'300', right:'700' });
 });
</script>

<div>
 <img  id="book" src="book.png" alt="" width="100" height="200" style="position: relative;left:600px"/>
</div>

simple kan ? hehehe
——————————
This article contained copyrighted material licensed under various creative commons licenses unless otherwise noted:
Image :
http://tpgblog.com/2009/11/30/jquery-plugin-its-cutetime-1-0-5/
Articles :
http://docs.jquery.com/Plugins/Authoring
http://net.tutsplus.com/articles/news/learn-how-to-create-a-jquery-plugin/
http://net.tutsplus.com/articles/news/you-still-cant-create-a-jquery-plugin/
http://jqfundamentals.com/book/book.html
jQuery 1.4 API ( CHM Version )

Zend Framework : ZendX_JQuery, View Helper untuk mengintegrasikan jQuery dan ZF

Posted in Javascript, Tutorial PHP, Zend Framework by samsonasik on August 1, 2010

ZendX merupakan Zend Framework Extensions Library , dibuat untuk meningkatkan kemampuan Zend Library itu sendiri secara lebih spesifik untuk kebutuhan-kebutuhan yang spesifik pula. Seperti halnya Zend_Dojo yang telah ada sebelumnya, ZenX_JQuery_View_Helper digunakan untuk mengintegrasikan jQuery dengan Zend Framework. ZendX_JQuery ini secara default mengimplementasikan Data Mashup,  di mana resource-resouce yang dibutuhkan ( dalam hal ini library jQuery) dipanggil secara outsource. Kita tidak perlu menginclude secara manual library jQuery-nya, karena otomatis akan dipanggil secara mashup, dalam kasus ini dari googleapis.

Berikut contoh kode programnya.
Pertama, kita set Helper untuk View dari Bootstrap.php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
 function _initView()
 {
 $view = new Zend_View();
 $view->addHelperPath('ZendX/JQuery/View/Helper/', 'ZendX_JQuery_View_Helper');

 $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
 $viewRenderer->setView($view);
 Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
 }
}

Setelah itu, kita tinggal panggil  di layout :

<html>
<head><title>JQuery and ZF Integration</title>
<?php echo $this->jQuery(); ?>
</head>
<body>
<?php echo $this->layout()->content; ?>
</body>
</html>

Ok, kalau sudah, kita bisa panggil di View :

<?php echo $this->ajaxLink("Show me something",
 $this->baseUrl()."/partialrenderer/loadhello",
 array('update' => '#content'));?>

Kalau di view source, akan tergenerate secara otomatis javascript seperti di bawah ini :

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
//<!--
$(document).ready(function() {
    $('a.ajaxLink1').click(function() { $.get('/zftutwordpress/public/partialrenderer/loadhello', {}, function(data, textStatus) { $('#content').html(data); }, 'html');return false; });
});
//-->
</script>

Lalu bagaimana jika tidak tersedia jaringan internet ? Kita bisa setlocalpath-nya dengan cara mengubah kode programnya menjadi seperti di bawah ini :

<html>
<head><title>JQuery and ZF Integration</title>
<?php echo $this->jQuery()->setLocalPath(path/to/jquery.js); ?>
</head>
<body>
<?php echo $this->layout()->content; ?>
</body>
</html>

This article contained copyrighted material licensed under various creative commons licenses unless otherwise noted:
Photo :
1. http://www.lgeoresearch.com/wp-content/themes/arthemia-premium/scripts/timthumb.php?src=/img/MashupHandshake.jpg&w=150&h=150&zc=1&q=100
Articles :
1. http://framework.zend.com/manual/en/
2. http://code.google.com/p/zendx/

Object Oriented JavaScript

Posted in Javascript by samsonasik on July 30, 2010

Sudah Javascript, Object Oriented lagi ? Heu..heu…, OOP nya aja masih Oge-oge Pusing, xi.xi.xi. :D. Bayangkan, dalam Javascript, Array adalah Object, Function adalah Object, Object adalah Object, so, Object dalam javascript itu apa ? Object dalam javascript adalah koleksi pasangan nama-nilai. Nama-nama berupa string, dan value bisa berupa Strings, numbers, booleans, dan objects (haiyah, tambah puyeng dah 😛 ). Ok, Lanjutttt…

Jika value adalah function, kita bisa bilang bahwa itu adalah method. Ketika sebuah metode objek dipanggil, variabel ini disetel ke objek. method dapat memanggil instance variable dengan keyword this.

contoh :

function setJs(str)
{
   this.MemberStr = str;
}

Maka, Jika kita instansiasi menjadi object :

var objSetStr = new setJs('this is a string');

maka, objSetStr.MemberStr berisi value ‘this is a string’
Penggunaan keyword this menunjukkan bahwa atribut itu bermodifier public , bagaimana dengan atribut bermodifier private ?
Cara pembuatan private modifier adalah dengan var , contoh  :

function HaiFromJs(str){
 //memberattr adalah atribut bermodifier private
//ia tidak bisa dipanggil dari luar function secara langsung.
 var memberattr = str ;
 this.hai = function(){
 alert ( "Hai " +memberattr+"\n apa kabar ? ");
 }
 }

 //buat object
 var haiObj = new HaiFromJs( "Samsonasik" );
 haiObj.hai();

sumber :
http://www.crockford.com/javascript/private.html
http://t1.gstatic.com/images?q=tbn:RhNF4WHmbEsPXM::&t=1&usg=__3rGjJy1YhTIJOBhFLOXM7dzC8iM=
http://en.wikipedia.org/wiki/Javascript
http://en.wikipedia.org/wiki/Class_%28computer_science%29
John Resig : Secrets Of the Javascript Ninja.