开发者

in atk4, how do i use ajax to update a view

I currently have a page defined which displays some data in rows. At the end of each row, there is a view which shows a total which is extracted from mysql.

$r->add('View_PointsLeft', 'pleft', 'pointsleft')
           ->setPoints($row['points_left'])
           ->setBacklog($row['backlog_ref'])
           ->setID($row['id'].'-points-left');

The view is defined with a template like this

<!-- points left -->
<div  class='target points_left'>
  <div class='sticky green'>
    <div class='story'><?$backlog?></div>
    <div id='<?$name?>' class='big_points big_point_margin'><?$pointsleft?></div>
  </div>
</div>
<!-- end 0000-points-left -->

The data to populate the view is selected using a sql in the page which is looped through and the /lib/view/pointsleft.php code has set methods which are passed parameters from the page and update the fields in the template.

class View_PointsLeft extends View_Sticky {
  function init(){
    parent::init();
  }

  function setPoints($points){
    $this->template->set('pointsleft',$points);
    return $this;
  }

  function setBacklog($backlog){
    $this->template->set('backlog',$backlog);
    return $this;
  }

  function defaultTemplate(){
    return array('view/scrumwall/pointsleft');
  }
}

I want to update the database when something is changed on the page and also update the total view (to decrement the counter).

First, I'm wondering if i have approached this the wrong way (should each view should be self contained) - should i just pass the id field to the view, attach the relevant model to the view inside lib/view/pointsleft.php and call the set fields using the model values ?

Secondly, If i change it that way, does it then make it easier to update the view with a particular id when the database value is changed using ajax and if so , how do i do this ?

Thirdly - if i want to also trigger an update into the database based on an action on the client side javascript, where would i put this code e.g. in the non atk4 version of my code, i had a script called using $.post("update.php") which would update mysql. Where would i put such a script in ATK4 ?

Thanks in advance.


Update after answer from Romans

Man, ATK4 rocks ! - it does more than i expected and i was busy creating functions inside the view to populate each field name, so now having redone it using addModel,

the call from the page looks like this

 $r->add('View_PointsLeft', 'pleft', 'pointsleft')
               ->loadData($row['id']);

the templates/view looks like this

<div id='<?$name?>' class='target points_left'>
  <div class='sticky green'>
    <div class='story'><?$backlog_ref?></div>
    <div class='big_points big_point_margin'><?$points_left?></div>
  </div>
</div>

and the lib/view code looks like this

<?php
class View_PointsLeft extends View_Sticky {

   function loadData($id){
  $this->setModel('Story')->loadData($id);
   }

   function init(){
      parent::init();
   }

   function defaultTemplate(){
      return array('view/scrumwall/pointsleft');
   }
}

Update after code example from Romans

After following the code example Romans provided, i now add the URL call using jquery selectors at the bottom of my page code and do some jiggery pokery to get the task and status from the id fields (not sure about using HTML5 only stufff using data-id so just set the normal id and extract from that). Previously the drop code was in my own univ.js script but i dont have access to the php variables from there so i move it into the page

 $p->js(true)->_selector('.movable')->draggable();
 $p->js(true)->_selector('.target')->droppable(array(
                   'drop'=>$this->js(null,'function(event,ui){'.
                           ' target=$(this).a开发者_StackOverflow中文版ttr("id").split("-");'.
                   ' zone=target[2];'.
                               ' sticky=$(ui.draggable).attr("id").split("-");'.
                               ' task=sticky[1];'.
                               ' switch (zone) {'.
                   ' case "verify": newStatus="V";'.
                   '                break;'.
                   ' case "in":     newStatus="P";'.
                   '                break;'.
                   ' case "to":     newStatus="I";'.
                   '                break;'.
                   ' case "done":   newStatus="D";'.
                               '                break;'.
                               '}
                              $.univ().ajaxec({ 0:"'.$this->api->getDestinationURL().'",'. 
                                  'task: task, status: newStatus }); } ')
    ));

and i have a if block which looks like this in the page. I add Model_Task and load the values based on the GET parameter so i then also have more information including the story it relates to so i can also update the points if the status is now done.

   if($_GET['task'] && $_GET['status'])
   {
        $new_status=$_GET['status'];
        $task_id=$_GET['task'];
        $t=$p->add('Model_Task')->loadData($task_id);
        $old_status=$t->get('status');
        $task_points=$t->get('points');

        if ($new_status<>$old_status & ($new_status=='D' | $old_status=='D'))
        {
           $s=$p->add('Model_Story')->loadData($t->get('story_id'));
           if ($old_status='D')
           {
             $s->set('points_left',$s->get('points_left')+$task_points);
           } else {
             $s->set('points_left',$s->get('points_left')-$task_points);
           }
           $s->update();

           $story=$t->get('story_id');
        }
    $t->set('status',$new_status);
    $t->update();
   }

i can then calculate the new number of points and update the story with points left and update the task with the new_status by setting the model values and using update().

If i now move one of the draggables, it works but opens a new window showing again the whole page and reporting

Error in AJAXec response: SyntaxError: syntax error

I think opening the extra window is because of the error but the error is something to do with the response having all the html for the whole page. I dont actually want any reload from the ajax call unless the status is a particular one.

Also the last thing i need to do is only reload one view on the page for the particular story that was updated.

I've tried by creating an array and adding the short variables to it like this when the page is first loaded

 $this->pl_arr[$row['id']]=$r->add('View_PointsLeft', 'pleft', 'pointsleft')
                         ->loadData($row['id']);

and then in the if block while processing the GET, to recall it

       $pleft=$this->pl_arr[$story];
       $pleft->js()->reload()->execute();

but it fails with an error

Error in AJAXec response: SyntaxError: missing ; before statement Fatal error: Call to a member function js() on a non-object in C:\wamp\www\paperless\page\scrumwall.php on line 247


Final update

The last error is caused because i didnt use for the id in the outer div of the whole view i wanted to update. Once i changed this it is no longer null.

So the first time the page is loaded, i store all the view names in an associative array in a loop as i put them on the page

    $st = $p->add('Model_Story');
    $result = $st->getRows();

    foreach ($result as $row) {
    if (is_array($row)) {
         $r=$p->add('View_Scrumwall_StoryRow')
               ->setWorkspace('ws-'.$row['id']);

        ... other code here ...

      $points_left[$row['id']]=$r->add('View_PointsLeft', null, 'pointsleft')
                      ->loadData($row['id']);
    }

and then have the if GET block like this

   if($_GET['task'] && $_GET['status'])
   {
        $new_status=$_GET['status'];
        $task_id=$_GET['task'];
        $t=$p->add('Model_Task')->loadData($task_id);
        $old_status=$t->get('status');
        $task_points=$t->get('points');

        if ($new_status<>$old_status && ($new_status=='D' || $old_status=='D'))
        {
           $s=$p->add('Model_Story')->loadData($t->get('story_id'));
           if ($new_status=='D')
           {
             $s->set('points_left',$s->get('points_left')-$task_points);
           } else {
             $s->set('points_left',$s->get('points_left')+$task_points);
           }
           $s->update();
           $story=$t->get('story_id');
           //reload the points left sticky note for the story of the task
           $js[]=$points_left[$story]->js()->reload();
        }
        $t->set('status',$new_status);
        $t->update();
        $js[]=$this->js()->reload();
        $this->js(null,$js)->execute();
   }

Note that if I only want to update one view on the page, i can just call that chaing that object with reload and execute e.g.

$pl->js()->reload()->execute

but if i want to update several views on the page, i need to put them in an array (here called js[]) and then call execute like this - you can also see an example of this in Roman's codepad example.

        $js[]=$points_left[$story]->js()->reload();

        $js[]=$this->js()->reload();
        $this->js(null,$js)->execute();

Problem solved with ATK4 :)


Ok, for a cleaner answer, I've put together a sample:

http://codepad.agiletoolkit.org/dragaction.html

Probably example here answers the question better.

In your case, since you are working with models, it should be easier to set this up. For the performance I decided to use 2 Listers, but in theory you can also have each person and task as a view.

I'm storing associations in session (through memorize) in your case you would store them in database.


Your structure seem to be OK. If you use setModel() on it which would have "pointsleft" and "backlog" fields, those would be automatically filled in.

I don't see how setID is defined though, but you could extend setModel, call parent and then execute that too.

Another thing I noticed, is in your template the most top-level div should have id=''. This gives your view unique selector which js() uses by default.

The .post function you are looking for is univ()->ajaxec(). It sends data to the server, receives javascript and executes it, hence the name. It behaves similarly to the form.

$mybutton->js('click')->ajaxec($this->getDestinationURL(null,array('a'=>'b'));

if($_GET['a']){
    // update database
    $r->getElement('plfat')->js()->reload()->execute();
}

Usually to make your code universal, you can drop this above code inside the view, but instead of 'a' you should better use the name of the object, like this. This eliminates the need for a separate page handling update:

$this->mybutton->js('click')->ajaxec($this->getDestinationURL(null,
     array($this->name=>'reload'));

if($_GET[$this->name]){
    // update database
    $this->js()->reload()->execute();
}

Update

To clarify the sequence of how it's executed:

  1. The page is rendered into HTML sent to your browser.
  2. Along with the page Javascript chains are sent. All of them which define 1st argument to js, such as js(true), js('click'). in my code i have js('click') so it's sent to browser.
  3. User performs the action such as clicking on a button. This triggers ajaxec() function
  4. ajaxec function performs AJAX request to the page with the arguments you specify there.
  5. PHP again is executed, but this time it goes inside if() branch. A js() without argument is created and ->execute() sends javascript to the browser.
  6. browser receives output of the js()...->execute() and evaluates it. In our case it contains reload() for some other element.
  7. atk4_loader widget is used to perform reload of other part of the page it sends AJAX request to server
  8. PHP is executed with cut_object argument. It re-initializes original page, but renders only one object selectively. Output for that object is sent back to the frontend.
  9. PHP also re-generates JS chains like in #2 but only relevant to that object
  10. Frontend's atk4_loader receives the code, replaces HTML of the element and re-evaluates the javascript.
  11. back to #3

It sounds like a lot of actions. In reality that's 2 requests per click and you can eliminate one if you do reload right away. Note that you can also pass arguments to reload() which you can then fetch from "get". I don't fully understand what triggers the action in your original script, perhaps we can find this out in https://chat.stackoverflow.com/rooms/2966/agile-toolkit-atk4 ?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜