Grails / Dojo进度条从控制器/服务获得进展

时间:2011-09-30 15:41:43

标签: ajax grails dojo

目前我正在使用Grails创建一个基于Web的数据加载应用程序,简而言之,它可以获取任意行的Excel表格,并通过后端系统运行它们来为测试人员准备数据。

一切都运行正常,但我需要的最后一件事是告知用户(特别是LARGE数据文件)已经处理了多少行数据的方法。如果有超过200行,应用程序将(显示)超时,即使它仍在继续。这是一个问题,因为用户很可能会重新加载文件并搞乱处理...重复的测试数据行将导致一堆下游问题。

我正在使用代码here

<g:actionSubmit action="${appContext}/FileUploader.processFile" value="Upload File" onclick="download()"></g:actionSubmit>

    <script type="text/javascript">
    dojo.require("dijit.ProgressBar");
    dojo.require("dojo.parser");

    var i = 0;
    function download() {
        jsProgress.update({
            maximum: 10,
            progress: ++i
        });
        if (i < 10) {
            setTimeout(download, 100 + Math.floor(Math.random() * 100));
        }
    }
</script>

目前在我的控制器中我有一个小方法可以做到这一点:

def updateStatus = {
    render uploaderService.rowsLoaded / uploaderService.listToSend.size()
}

我似乎无法弄清楚是调用方法来获取链接到进度条的百分比的正确方法。 (替换样板progress代码。)

我非常了解Java但是这个工作似乎有点神秘。

我愿意接受任何关于在其中取得进展的想法,无论其技术上的最佳实践......我到了我只需要SOMETHING来显示这些信息的地步。它不一定是道场,它只是我最初取得成功的方向。

2 个答案:

答案 0 :(得分:1)

首先,您可能希望启动后台作业中的进程并将处理消息返回给用户

以下是使用jprogress和executer插件执行此操作的示例。不幸的是,这使用了轮询解决方案。我还没弄清楚如何使用JMS来触发更新。

package jprogressdemo

class Event {

    String name
    Integer duration = 100
    String status = "New"
    Integer percentComplete = 0

    static mapping = {
      cache false
    }

    static constraints = {
        name(size:1..45, unique:true )
        duration()
        status(size:1..5)
        percentComplete()
    }
}

控制器

package jprogressdemo

class EventController {

    //static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
    def progressService
    def jmsService


    static exposes = ['jms']
    static destination = "queue.notification"


    def executeAction = {

        println "executeAction"

        def theEvent    = Event.get(params.id)

        def duration    = theEvent?.duration ?: 10
        def name        = theEvent?.name.trim() ?: "none"
        def startAt     = theEvent?.percentComplete ?: 0

        toEvent(name,duration,startAt,true)

        render "the progress is done"
    }

    /*
     * Start the backgorund task then
     * while %complete < 100, query db and update progressbar.
     */

    //the progress bar id needs to the same value that's passed into .setProgressBarValue
    def backgroundAction = {

        println "backgroundAction"

        println "isDisabled():${jmsService.isDisabled()}"

        def theEvent    = Event.get(params.id)

        def duration    = theEvent?.duration ?: 10
        def name        = theEvent?.name ?: "none"
        def barName        = "${name}b"
        def percentComplete     = theEvent?.percentComplete ?: 0
        def lastPct             = -1

        runAsync { 
            toEvent(name,duration,percentComplete,false)
        }

        if (percentComplete > 100) {progressService.setProgressBarValue(barName, 100)}  
        //can't be factored out because it's this function that 
        // gets called from the client ????
        while(percentComplete <= 100) {
            println "percentComplete:${percentComplete}"
            if (percentComplete != lastPct ) {
                progressService.setProgressBarValue(barName, percentComplete)
                lastPct = percentComplete
            }
            def newEvent    = Event.get(params.id)
            newEvent.refresh()
            percentComplete = theEvent.percentComplete
        }


        render "the progress is done"
    }


    //the progress bar id needs to the same value that's passed into .setProgressBarValue
    def backgroundProgress = {

        def theEvent    = Event.get(params.id)

        def duration    = theEvent?.duration ?: 10
        def name        = theEvent?.name ?: "none"
        def barName        = "${name}p"
        def percentComplete     = theEvent?.percentComplete ?: 0
        def lastPct             = -1

        if (percentComplete > 100) {progressService.setProgressBarValue(barName, 100)}  

        while(percentComplete <= 100) {
            println "percentComplete:${percentComplete}"
            if (percentComplete != lastPct ) {
                progressService.setProgressBarValue(barName, percentComplete)
                lastPct = percentComplete
            }
            def newEvent    = Event.get(params.id)
            newEvent.refresh()
            percentComplete = theEvent.percentComplete
        }

    }
    /*

    % complete needs to get to 101 to avoid infinit loop in polling logic
    And you can't go from 0-99 because the progress bar doesn't register a 0

     */

    def toEvent(name,duration,startAt,updateBar) {

        println "duration:${duration}"
        println "name:${name}"
        println "startat:${startAt}"

        for (int i = startAt; i < 102; i++) {

            println "i:${i}"

            def theEvent = Event.findByName(name)
            theEvent.percentComplete = i
            theEvent.save(flush:true)
            println "theEvent.percentComplete:${theEvent.percentComplete}"

            if(updateBar){
                progressService.setProgressBarValue(name, i)
            } else {
                sendJMSMessage("queue.notification", "${i}")
            }

            //let's waste some time
            for (int a = 0; a < duration; a++) {

                for (int b = 0; b < 1000; b++) {

                }
            }
        }
    }



    def index = {
        redirect(action: "list", params: params)
    }

    def list = {
        params.max = Math.min(params.max ? params.int('max') : 10, 100)
        [eventInstanceList: Event.list(params), eventInstanceTotal: Event.count()]
    }

    def create = {
        def eventInstance = new Event()
        eventInstance.properties = params
        return [eventInstance: eventInstance]
    }

    def save = {
        def eventInstance = new Event(params)
        if (eventInstance.save(flush: true)) {
            flash.message = "${message(code: 'default.created.message', args: [message(code: 'event.label', default: 'Event'), eventInstance.id])}"
            redirect(action: "show", id: eventInstance.id)
        }
        else {
            render(view: "create", model: [eventInstance: eventInstance])
        }
    }

    def show = {
        def eventInstance = Event.get(params.id)
        if (!eventInstance) {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'event.label', default: 'Event'), params.id])}"
            redirect(action: "list")
        }
        else {
            [eventInstance: eventInstance]
        }
    }

    def edit = {
        def eventInstance = Event.get(params.id)
        if (!eventInstance) {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'event.label', default: 'Event'), params.id])}"
            redirect(action: "list")
        }
        else {
            return [eventInstance: eventInstance]
        }
    }

    def update = {
        def eventInstance = Event.get(params.id)
        if (eventInstance) {
            if (params.version) {
                def version = params.version.toLong()
                if (eventInstance.version > version) {

                    eventInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'event.label', default: 'Event')] as Object[], "Another user has updated this Event while you were editing")
                    render(view: "edit", model: [eventInstance: eventInstance])
                    return
                }
            }
            eventInstance.properties = params
            if (!eventInstance.hasErrors() && eventInstance.save(flush: true)) {
                flash.message = "${message(code: 'default.updated.message', args: [message(code: 'event.label', default: 'Event'), eventInstance.id])}"
                redirect(action: "show", id: eventInstance.id)
            }
            else {
                render(view: "edit", model: [eventInstance: eventInstance])
            }
        }
        else {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'event.label', default: 'Event'), params.id])}"
            redirect(action: "list")
        }
    }

    def delete = {
        def eventInstance = Event.get(params.id)
        if (eventInstance) {
            try {
                eventInstance.delete(flush: true)
                flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'event.label', default: 'Event'), params.id])}"
                redirect(action: "list")
            }
            catch (org.springframework.dao.DataIntegrityViolationException e) {
                flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'event.label', default: 'Event'), params.id])}"
                redirect(action: "show", id: params.id)
            }
        }
        else {
            flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'event.label', default: 'Event'), params.id])}"
            redirect(action: "list")
        }
    }
}

查看

<%@ page import="jprogressdemo.Event" %>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="layout" content="main" />
   <g:javascript library="jquery" plugin="jquery"/>
  <jqui:resources/>
  <g:set var="entityName" value="${message(code: 'event.label', default: 'Event')}" />
  <title><g:message code="default.show.label" args="[entityName]" /></title>
</head>
<body>
  <div class="nav">
    <span class="menuButton"><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></span>
    <span class="menuButton"><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></span>
    <span class="menuButton"><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></span>
  </div>
  <div class="body">
    <h1><g:message code="default.show.label" args="[entityName]" /></h1>
    <g:if test="${flash.message}">
      <div class="message">${flash.message}</div>
    </g:if>
    <div class="dialog">
      <table>
        <tbody>

          <tr class="prop">
            <td valign="top" class="name"><g:message code="event.id.label" default="Id" /></td>

        <td valign="top" class="value">${fieldValue(bean: eventInstance, field: "id")}</td>

        </tr>

        <tr class="prop">
          <td valign="top" class="name"><g:message code="event.name.label" default="Name" /></td>

        <td valign="top" class="value">${fieldValue(bean: eventInstance, field: "name")}</td>

        </tr>

        <tr class="prop">
          <td valign="top" class="name"><g:message code="event.duration.label" default="Duration" /></td>

        <td valign="top" class="value">${fieldValue(bean: eventInstance, field: "duration")}</td>

        </tr>

        <tr class="prop">
          <td valign="top" class="name"><g:message code="event.status.label" default="Status" /></td>

        <td valign="top" class="value">${fieldValue(bean: eventInstance, field: "status")}</td>

        </tr>

        <tr class="prop">
          <td valign="top" class="name"><g:message code="event.percentComplete.label" default="Percent Complete" /></td>

        <td valign="top" class="value">${fieldValue(bean: eventInstance, field: "percentComplete")}</td>

        </tr>

        </tbody>
      </table>
    </div>
    <div class="buttons">
      <g:form>
        <g:hiddenField name="id" value="${eventInstance?.id}" />
        <span class="button"><g:actionSubmit class="edit" action="edit" value="${message(code: 'default.button.edit.label', default: 'Edit')}" /></span>
        <span class="button"><g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" /></span>
      </g:form>
    </div>
    <p>
    <HR WIDTH="75%" COLOR="#FF0000" SIZE="4"/>
    <g:form>
      <g:hiddenField name="id" value="${eventInstance?.id}"/>   
      <g:submitToRemote action="executeAction"  name="startButton" value="start...."/>
      <g:submitToRemote action="backgroundAction"  name="backgroundButton" value="background...."/>
      <g:submitToRemote action="backgroundProgress"  name="progressButton" value="progress...."/>
    </g:form>

    <g:jprogress progressId="${eventInstance?.name}" trigger="startButton"/>
    <g:jprogress progressId="${eventInstance?.name}b" trigger="backgroundButton"/>
    <g:jprogress progressId="${eventInstance?.name}p" trigger="progressButton"/>

  </div>
</body>
</html>

答案 1 :(得分:0)

您还可以使用CometD插件向主题发布消息,并让您的进度条订阅该主题...

http://metasieve.wordpress.com/2010/08/25/using-cometd-2-x-with-grails/