Viewed   91 times

So far, I have figured out how to return a typical JSON response in Zend Framework 2. First, I added the ViewJsonStrategy to the strategies section of the view_manager configuration. Then, instead of returning a ViewModel instance from the controller action, I return a JsonModel instance with all my variables set.

Now that I've figured that piece out, I need to understand how to render a view and return it within that JSON response. In ZF1, I was able to use $this->view->render($scriptName), which returned the HTML as a string. In ZF2, the ZendViewView::render(...) method returns void.

So... how can I render an HTML view script and return it in a JSON response in one request?

This is what I have right now:

    if ($this->getRequest()->isXmlHttpRequest()) {
        $jsonModel = new JsonModel(...);

        /* @todo Render HTML script into `$html` variable, and add to `JsonModel` */
        return $jsonModel;
    } else {
        return new ViewModel(...);
    }

 Answers

5

OK, i think i finally understood what you're doing. I've found a solution that i think matches your criteria. Though i am sure that there is room for improvement, as there's some nasty handwork to be done...

public function indexAction()
{
  if (!$this->getRequest()->isXmlHttpRequest()) {
    return array();
  }

  $htmlViewPart = new ViewModel();
  $htmlViewPart->setTerminal(true)
               ->setTemplate('module/controller/action')
               ->setVariables(array(
                  'key' => 'value'
               ));

  $htmlOutput = $this->getServiceLocator()
                     ->get('viewrenderer')
                     ->render($htmlViewPart);

  $jsonModel = new JsonModel();
  $jsonModel->setVariables(array(
    'html' => $htmlOutput,
    'jsonVar1' => 'jsonVal2',
    'jsonArray' => array(1,2,3,4,5,6)
  ));

  return $jsonModel;
}

As you can see, the templateMap i create is ... nasty ... it's annoying and i'm sure it can be improved by quite a bit. It's a working solution but just not a clean one. Maybe somehow one would be able to grab the, probably already instantiated, default PhpRenderer from the ServiceLocator with it's template- and path-mapping and then it should be cleaner.

Thanks to the comment ot @DrBeza the work needed to be done could be reduced by a fair amount. Now, as I'd initially wanted, we will grab the viewrenderer with all the template mapping intact and simply render the ViewModel directly. The only important factor is that you need to specify the fully qualified template to render (e.g.: "$module/$controller/$action")

I hope this will get you started though ;)

PS: Response looks like this:

Object:
    html: "<h1>Hello World</h1>"
    jsonArray: Array[6]
    jsonVar1: "jsonVal2"
Friday, October 14, 2022
5

1)

You can use any model from any module in your application, so they can be "shared". Fro example you use the ServiceManager to help you grab instances of models (and other classes) in your project.

Service Manager Config:

'factories' => array(
    'AuthService' => function($sm) {
        $auth = new ZendAuthenticationAuthenticationService();

        return $auth;
    },
    'RoleMapper' => function($sm) {
        $dbAdapter = $sm->get('ZendDbAdapterAdapter');
        $mapper = new ApplicationMapperRoleMapper;
        $mapper->setDbAdapter($dbAdapter);
        $mapper->setEntityPrototype(new ApplicationModelRole);
        $mapper->setHydrator(new ApplicationModelRoleHydrator);

        return $mapper;
    },
    'UserMapper' => function($sm) {
        $dbAdapter = $sm->get('ZendDbAdapterAdapter');
        $mapper = new MyModuleMapperUserMapper;
        $mapper->setDbAdapter($dbAdapter);
        $mapper->setEntityPrototype(new MyModuleModelUser);
        $mapper->setHydrator($sm->get('UserHydrator'));

        return $mapper;
    },
    ....

As you can see you can have classes from multiple modules defined in there. Usually you would just define a separate ServiceManager config for each module though.

There's nothing to stop you from making an instance of "UserMapper" inside your Application module like this:

Some Controller:

$this->getServiceLocator()->get('UserMapper');
// Or even grab Zend libraries like this
$this->getServiceLocator()->get('AuthService');

The ServiceManager will allow you to grab instances of classes from any of your modules inside any of the others with a problem.

2)

The service manager doesn't actually make an instance of anything until you request it, so there's no overhead as you suggest.

From the example above, there's no instances actually created until you first ask for one:

$this->getServiceLocator()->get('UserMapper'); // instance now created.

If you never ask the service manager for a "RoleMapper" for example, then no instance is created.

Monday, October 3, 2022
 
alc6379
 
1

You can create a container class and use it in both controllers

in module.conf

public function getServiceConfig()
{
    return array(
        'invokables' => array(
            'my_handy_container'        => 'pathcontainer_class_name',
        )
    );
}

Create a getter in both controllers:

public function getMyHandyContainer()
{
    if (!$this->myHandyContainer) {
        $this->myHandyContainer = $this->getServiceLocator()->get('my_handy_container');
    }
    return $this->myHandyContainer;
}

And call it using:

$myContainer = $this->getMyHandyContainer()->myHandyContainer;
$myContainer->foo = 5; // set something

ZF2 way to pass vars using forward

In the passing method do:

return $this->forward()->dispatch('controller_name', [
    'action' => 'whatever',
    'varname' => $value,
    'varname2' => $value2
]);

In the invoked controller method, do:

$param2 = $this->params()->fromRoute('varname2',false);
Monday, December 26, 2022
 
3

RenderPartialView is a custom extension method which renders a view as a string.

It wasn't mentioned in the article (what you have referred originally) but you find it in the sample code attached to the article. It can be found under the HelpersReders.cs

Here is code of the method in question:

public static string RenderPartialView(this Controller controller, 
    string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = controller.ControllerContext.RouteData
            .GetRequiredString("action");

    controller.ViewData.Model = model;
    using (var sw = new StringWriter())
    {
        ViewEngineResult viewResult = ViewEngines.Engines
            .FindPartialView(controller.ControllerContext, viewName);
        var viewContext = new ViewContext(controller.ControllerContext, 
            viewResult.View, controller.ViewData, controller.TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
    }
}
Tuesday, September 6, 2022
5

You need to create Root class which contains data property. See below model:

class Root {

    private Data data;

    // getters, setters, toString
}

class Data {
    LeagueTable[] leagueTable;

    // getters, setters, toString

    public static class LeagueTable{
        String team;
        int played, gamesWon, gamesDraw, gameLost, goalsFor, goalsAgainst, goalsDifference, points;

        // getters, setters, toString
    }
}

You can deserialise it as below:

Data data = new Gson().fromJson(dataRead, Root.class).getData();

See also:

  • Skip root element while deserializing json
  • Gson Deserialization Cookbook
  • Array of JSON Object to Java POJO
Friday, September 23, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :