Пример #1
0
class ListWidget(html5.Div):
    """
		Provides the interface to list-applications.
		It acts as a data-provider for a DataTable and binds an action-bar
		to this table.
	"""
    def __init__(self,
                 module,
                 filter=None,
                 columns=None,
                 isSelector=False,
                 filterID=None,
                 filterDescr=None,
                 batchSize=None,
                 context=None,
                 autoload=True,
                 *args,
                 **kwargs):
        """
			@param module: Name of the modul we shall handle. Must be a list application!
			@type module: string
		"""
        if not module in conf["modules"].keys():
            conf["mainWindow"].log(
                "error",
                translate("The module '{module}' does not exist.",
                          module=module))
            assert module in conf["modules"].keys()

        super(ListWidget, self).__init__()
        self._batchSize = batchSize or conf[
            "batchSize"]  # How many rows do we fetch at once?
        self.isDetaching = False  #If set, this widget is beeing about to be removed - dont issue nextBatchNeeded requests
        self.module = module
        self.context = context

        self.actionBar = ActionBar(module, "list", currentAction="list")
        self.appendChild(self.actionBar)

        self.sideBar = SideBar()
        self.appendChild(self.sideBar)

        myView = None

        if filterID:
            if conf["modules"] and module in conf["modules"].keys():
                if "views" in conf["modules"][module].keys(
                ) and conf["modules"][module]["views"]:
                    for v in conf["modules"][module]["views"]:
                        if v["__id"] == filterID:
                            myView = v
                            break
            if myView and "extendedFilters" in myView.keys(
            ) and myView["extendedFilters"]:
                self.appendChild(CompoundFilter(myView, module, embed=True))

        checkboxes = (conf["modules"] and module in conf["modules"].keys() and
                      "checkboxSelection" in conf["modules"][module].keys()
                      and conf["modules"][module]["checkboxSelection"])
        indexes = (conf["modules"] and module in conf["modules"].keys()
                   and "indexes" in conf["modules"][module].keys()
                   and conf["modules"][module]["indexes"])

        self.table = DataTable(checkboxes=checkboxes,
                               indexes=indexes,
                               *args,
                               **kwargs)
        self.appendChild(self.table)
        self._currentCursor = None
        self._structure = None
        self._currentRequests = []
        self.columns = []

        if isSelector and filter is None and columns is None:
            #Try to select a reasonable set of cols / filter
            if conf["modules"] and module in conf["modules"].keys():
                tmpData = conf["modules"][module]
                if "columns" in tmpData.keys():
                    columns = tmpData["columns"]
                if "filter" in tmpData.keys():
                    filter = tmpData["filter"]

        self.table.setDataProvider(self)
        self.filter = filter.copy() if isinstance(filter, dict) else {}
        self.columns = columns[:] if isinstance(columns, list) else []
        self.filterID = filterID  #Hint for the sidebarwidgets which predefined filter is currently active
        self.filterDescr = filterDescr  #Human-readable description of the current filter
        self._tableHeaderIsValid = False
        self.isSelector = isSelector

        #Proxy some events and functions of the original table
        for f in [
                "selectionChangedEvent", "selectionActivatedEvent",
                "cursorMovedEvent", "tableChangedEvent", "getCurrentSelection"
        ]:
            setattr(self, f, getattr(self.table, f))

        self.actionBar.setActions(self.getDefaultActions(myView))

        if isSelector:
            self.selectionActivatedEvent.register(self)

        self.emptyNotificationDiv = html5.Div()
        self.emptyNotificationDiv.appendChild(
            html5.TextNode(translate("Currently no entries")))
        self.emptyNotificationDiv["class"].append("emptynotification")
        self.appendChild(self.emptyNotificationDiv)
        self.emptyNotificationDiv["style"]["display"] = "none"
        self.table["style"]["display"] = "none"
        self.filterDescriptionSpan = html5.Span()
        self.appendChild(self.filterDescriptionSpan)
        self.filterDescriptionSpan["class"].append("filterdescription")
        self.updateFilterDescription()

        if autoload:
            self.reloadData()

    def updateFilterDescription(self):
        self.filterDescriptionSpan.removeAllChildren()

        if self.filterDescr:
            self.filterDescriptionSpan.appendChild(
                html5.TextNode(html5.utils.unescape(self.filterDescr)))

    def getDefaultActions(self, view=None):
        """
			Returns the list of actions available in our actionBar
		"""
        defaultActions = ["add", "edit", "clone", "delete",
                          "|", "preview", "selectfields"]\
                         + (["|", "select","close"] if self.isSelector else [])+["|", "reload","selectfilter"]

        #if not self.isSelector:
        #	defaultActions += ["|", "exportcsv"]

        # Extended actions from view?
        if view and "actions" in view.keys():
            if defaultActions[-1] != "|":
                defaultActions.append("|")

            defaultActions.extend(view["actions"] or [])

        # Extended Actions from config?
        elif conf["modules"] and self.module in conf["modules"].keys():
            cfg = conf["modules"][self.module]

            if "actions" in cfg.keys() and cfg["actions"]:
                if defaultActions[-1] != "|":
                    defaultActions.append("|")

                defaultActions.extend(cfg["actions"])

        return defaultActions

    def showErrorMsg(self, req=None, code=None):
        """
			Removes all currently visible elements and displayes an error message
		"""
        self.actionBar["style"]["display"] = "none"
        self.table["style"]["display"] = "none"
        errorDiv = html5.Div()
        errorDiv["class"].append("error_msg")
        if code and (code == 401 or code == 403):
            txt = translate("Access denied!")
        else:
            txt = translate("An unknown error occurred!")
        errorDiv["class"].append("error_code_%s" % (code or 0))
        errorDiv.appendChild(html5.TextNode(txt))
        self.appendChild(errorDiv)

    def onNextBatchNeeded(self):
        """
			Requests the next rows from the server and feed them to the table.
		"""
        if self._currentCursor and not self.isDetaching:
            filter = {}

            if self.context:
                filter.update(self.context)

            filter.update(self.filter)
            filter["amount"] = self._batchSize
            filter["cursor"] = self._currentCursor

            self._currentRequests.append(
                NetworkService.request(self.module,
                                       "list",
                                       filter,
                                       successHandler=self.onCompletion,
                                       failureHandler=self.showErrorMsg,
                                       cacheable=True))
            self._currentCursor = None
        else:
            self.table.setDataProvider(None)

    def onAttach(self):
        super(ListWidget, self).onAttach()
        NetworkService.registerChangeListener(self)

    def onDetach(self):
        self.isDetaching = True
        super(ListWidget, self).onDetach()
        NetworkService.removeChangeListener(self)

    def onDataChanged(self, module, **kwargs):
        """
			Refresh our view if element(s) in this modul have changed
		"""
        if module and module != self.module:
            return

        self.reloadData()

    def reloadData(self):
        """
			Removes all currently displayed data and refetches the first batch from the server.
		"""
        self.table.clear()
        self._currentCursor = None
        self._currentRequests = []

        filter = {}
        if self.context:
            filter.update(self.context)

        filter.update(self.filter)
        filter["amount"] = self._batchSize

        self._currentRequests.append(
            NetworkService.request(self.module,
                                   "list",
                                   filter,
                                   successHandler=self.onCompletion,
                                   failureHandler=self.showErrorMsg,
                                   cacheable=True))

    def setFilter(self, filter, filterID=None, filterDescr=None):
        """
			Applies a new filter.
		"""
        self.filter = filter
        self.filterID = filterID
        self.filterDescr = filterDescr
        self.updateFilterDescription()
        self.reloadData()

    def setContext(self, context):
        """
			Applies a new context.
		"""
        self.context = context
        self.reloadData()

    def getFilter(self):
        if self.filter:
            return ({k: v for k, v in self.filter.items()})
        return ({})

    def onCompletion(self, req):
        """
			Pass the rows received to the datatable.
			@param req: The network request that succeed.
		"""
        if not req in self._currentRequests:
            return

        self._currentRequests.remove(req)
        self.actionBar.resetLoadingState()

        data = NetworkService.decode(req)

        if data["structure"] is None:
            if self.table.getRowCount():
                self.table.setDataProvider(
                    None)  #We cant load any more results
            else:
                self.table["style"]["display"] = "none"
                self.emptyNotificationDiv["style"]["display"] = ""
                #self.element.innerHTML = "<center><strong>Keine Ergebnisse</strong></center>"
            return

        self.table["style"]["display"] = ""
        self.emptyNotificationDiv["style"]["display"] = "none"
        self._structure = data["structure"]

        if not self._tableHeaderIsValid:
            if not self.columns:
                self.columns = []
                for boneName, boneInfo in data["structure"]:
                    if boneInfo["visible"]:
                        self.columns.append(boneName)
            self.setFields(self.columns)

        if data["skellist"] and "cursor" in data.keys():
            self._currentCursor = data["cursor"]
            self.table.setDataProvider(self)
        else:
            self.table.setDataProvider(None)

        self.table.extend(data["skellist"])

    def setFields(self, fields):
        if not self._structure:
            self._tableHeaderIsValid = False
            return

        boneInfoList = []
        tmpDict = {key: bone for key, bone in self._structure}

        fields = [x for x in fields if x in tmpDict.keys()]
        self.columns = fields

        for boneName in fields:
            boneInfo = tmpDict[boneName]
            delegateFactory = viewDelegateSelector.select(
                self.module, boneName, tmpDict)(self.module, boneName, tmpDict)
            self.table.setCellRender(boneName, delegateFactory)
            boneInfoList.append(boneInfo)

        if conf["showBoneNames"]:
            self.table.setHeader(fields)
        else:
            self.table.setHeader([x.get("descr", "") for x in boneInfoList])

        self.table.setShownFields(fields)
        rendersDict = {}

        for boneName in fields:
            boneInfo = tmpDict[boneName]
            delegateFactory = viewDelegateSelector.select(
                self.module, boneName, tmpDict)(self.module, boneName, tmpDict)
            rendersDict[boneName] = delegateFactory
            boneInfoList.append(boneInfo)

        self.table.setCellRenders(rendersDict)
        self._tableHeaderIsValid = True

    def getFields(self):
        return (self.columns[:])

    def onSelectionActivated(self, table, selection):
        conf["mainWindow"].removeWidget(self)

    def activateCurrentSelection(self):
        """
			Emits the selectionActivated event if there's currently a selection

		"""
        self.table.activateCurrentSelection()

    @staticmethod
    def canHandle(moduleName, moduleInfo):
        return moduleInfo["handler"] == "list" or moduleInfo[
            "handler"].startswith("list.")

    @staticmethod
    def render(moduleName, adminInfo, context):

        filter = adminInfo.get("filter")
        columns = adminInfo.get("columns")

        return ListWidget(module=moduleName,
                          filter=filter,
                          columns=columns,
                          context=context)
Пример #2
0
class RepeatDatePopup(html5.Div):

	__editIdx_ = 0

	def __init__(self, modul, key):
		super(RepeatDatePopup, self).__init__()
		self.module = modul
		self.editIdx = RepeatDatePopup.__editIdx_ #Iternal counter to ensure unique ids
		RepeatDatePopup.__editIdx_ += 1
		self.key = key
		self._lastData = {} #Dict of structure and values received
		self.closeOnSuccess = False

		h3 = html5.H3()
		h3["class"].append("modul_%s" % self.module)
		h3["class"].append("apptype_list")

		h3.appendChild(html5.TextNode(translate("create recurrent dates")))

		self.wasInitialRequest = True
		self.actionbar = ActionBar( self.module, "list", "repeatdate")
		self.appendChild( self.actionbar )
		self.form = html5.Form()
		self.appendChild(self.form)
		self.actionbar.setActions(["create.recurrent"])
		self.reloadData()

	def reloadData(self):
		self.save({})
		return

	def save(self, data):
		self.wasInitialRequest = not len(data)>0
		if self.module=="_tasks":
			return #FIXME!
		else:
			if not data:
				NetworkService.request(self.module,"view/%s" % self.key, successHandler=self.setData, failureHandler=self.showErrorMsg)
			else:
				NetworkService.request(self.module, "add", data, secure=len(data)>0, successHandler=self.setData, failureHandler=self.showErrorMsg )

	def setData( self, request=None, data=None, ignoreMissing=False ):
		"""
		Rebuilds the UI according to the skeleton received from server

		@param request: A finished NetworkService request
		@type request: NetworkService
		@type data: dict
		@param data: The data received
		"""
		assert (request or data)
		if request:
			data = NetworkService.decode( request )

		try:
			skelStructure = {k: v for k, v in data["structure"]}

		except AttributeError:
			NetworkService.notifyChange(self.module)
			conf["mainWindow"].removeWidget( self )
			return

		print
		print("data", data)
		print("action", data["action"])
		if "action" in data and (data["action"] in ["addSuccess", "editSuccess"]):
			NetworkService.notifyChange(self.module)
			logDiv = html5.Div()
			logDiv["class"].append("msg")
			spanMsg = html5.Span()
			spanMsg.appendChild( html5.TextNode( translate("Entry saved!") ))
			spanMsg["class"].append("msgspan")
			logDiv.appendChild(spanMsg)
			if self.module in conf["modules"].keys():
				spanMsg = html5.Span()
				spanMsg.appendChild( html5.TextNode( conf["modules"][self.module]["name"] ))
				spanMsg["class"].append("modulspan")
				logDiv.appendChild(spanMsg)
			if "values" in data.keys() and "name" in data["values"].keys():
				spanMsg = html5.Span()
				spanMsg.appendChild( html5.TextNode( str(data["values"]["name"]) ))
				spanMsg["class"].append("namespan")
				logDiv.appendChild(spanMsg)
			conf["mainWindow"].log("success",logDiv)
			if self.closeOnSuccess:
				conf["mainWindow"].removeWidget( self )
				return
			self.clear()
			# self.bones = {}
			self.reloadData()
			return

		self.clear()
		self.actionbar.resetLoadingState()
		self.dataCache = data

		fieldSets = {}
		cat = "byweek"
		fs = html5.Fieldset()
		fs["class"] = cat
		if cat=="byweek":
			fs["class"].append("active")

		fs["name"] = cat
		legend = html5.Legend()
		fshref = fieldset_A()
		fshref.appendChild(html5.TextNode(cat) )
		legend.appendChild( fshref )
		fs.appendChild(legend)
		section = html5.Section()
		fs.appendChild(section)
		fs._section = section
		fieldSets[ cat ] = fs

		self.dtstart = data["values"]["startdate"]
		startdateLabel = html5.Label("Termin")
		startdateLabel["class"].append("termin")
		startdateLabel["class"].append("date")
		startdate_id = "vi_%s_%s_edit_bn_%s" % ( self.editIdx, self.module, "repeatdate")
		startdateLabel["for"] = startdate_id
		startdate = date.DateViewBoneDelegate("termin", "startdate", skelStructure).render(data["values"], "startdate")
		startdate["id"] = startdate_id
		containerDiv = html5.Div()
		containerDiv.appendChild(startdateLabel)
		containerDiv.appendChild(startdate)
		containerDiv["class"].append("bone")
		containerDiv["class"].append("bone_startdate")
		containerDiv["class"].append("date")
		fieldSets[ cat ]._section.appendChild( containerDiv )

		countLabel = html5.Label("Wiederholungen")
		countLabel["class"].append("count")
		countLabel["class"].append("numeric")
		count_id = "vi_%s_%s_edit_bn_%s" % ( self.editIdx, self.module, "count")
		countLabel["for"] = count_id

		self.count = html5.Input()
		self.count["id"] = count_id
		containerDiv2 = html5.Div()
		containerDiv2["class"].append("bone")
		containerDiv2["class"].append("bone_count")
		containerDiv2["class"].append("date")
		containerDiv2.appendChild(countLabel)
		containerDiv2.appendChild(self.count)

		# containerDiv3 = html5.Div()
		# self.byweekday = list()
		# for key, value in [["MO", "Mo"], ["TU", "Di"], ["TH", "Mi"], ["WE", "Do"], ["FR", "Fr"], ["SA", "Sa"], ["SU", "So"]]:
		# 	alabel=html5.Label()
		# 	acheckbox=html5.Input()
		# 	acheckbox["type"]="checkbox"
		# 	acheckbox["name"]=key
		# 	alabel.appendChild(acheckbox)
		# 	aspan=html5.Span()
		# 	aspan.element.innerHTML=value
		# 	alabel.appendChild(aspan)
		# 	containerDiv3.appendChild(alabel)
		# 	containerDiv2["class"].append("bone")
		# 	containerDiv2["class"].append("bone_count")
		# 	containerDiv2["class"].append("byweekday")
		# 	self.byweekday.append(acheckbox)

		fieldSets[ cat ]._section.appendChild(containerDiv2)
		# fieldSets[ cat ]._section.appendChild(containerDiv3)
		for (k,v) in fieldSets.items():
			if not "active" in v["class"]:
				v["class"].append("active")
		tmpList = [(k,v) for (k,v) in fieldSets.items()]
		tmpList.sort( key=lambda x:x[0])
		for k,v in tmpList:
			self.form.appendChild( v )
			v._section = None


	def clear(self):
		"""
			Removes all visible bones/forms/fieldsets.
		"""
		for c in self.form._children[ : ]:
			self.form.removeChild( c )

	def showErrorMsg(self, req=None, code=None):
		"""
			Removes all currently visible elements and displayes an error message
		"""
		self.actionbar["style"]["display"] = "none"
		self.form["style"]["display"] = "none"
		errorDiv = html5.Div()
		errorDiv["class"].append("error_msg")
		if code and (code==401 or code==403):
			txt = translate("Access denied!")
		else:
			txt = translate("An unknown error occurred!")
		errorDiv["class"].append("error_code_%s" % (code or 0))
		errorDiv.appendChild( html5.TextNode( txt ) )
		self.appendChild( errorDiv )

	def doSave( self, closeOnSuccess=False):
		self.closeOnSuccess = closeOnSuccess
		data ={"count": self.count._getValue(), "kind" : "2", "dtstart" : self.dtstart
		# , "byweekday" : [box["name"] for box in self.byweekday if box["checked"]]
		}
		# r = rrule.rrule(2, dtstart=datetime(2014, 7,1, 18,00), count=7)
		NetworkService.request(self.module, "addrecurrent/%s" % self.key, data, secure=True, successHandler=self.setData, failureHandler=self.showErrorMsg )
Пример #3
0
class TreeWidget(html5.Div):
    nodeWidget = NodeWidget
    leafWidget = LeafWidget
    defaultActions = [
        "add.node", "add.leaf", "selectrootnode", "edit", "delete", "reload"
    ]

    def __init__(self,
                 module,
                 rootNode=None,
                 node=None,
                 selectMode=None,
                 *args,
                 **kwargs):
        """
			:param module: Name of the module we shall handle. Must be a list application!
			:type module: str
			:param rootNode: The rootNode we shall display. If None, we try to select one.
			:type rootNode: str or None
			:param node: The node we shall display at start. Must be a child of rootNode
			:type node: str or None
		"""
        super(TreeWidget, self).__init__()
        self.addClass("tree")

        self.module = module
        self.rootNode = rootNode
        self.node = node or rootNode
        self.actionBar = ActionBar(module, "tree")
        self.appendChild(self.actionBar)
        self.pathList = html5.Ul()
        self.pathList["class"].append("breadcrumb")
        self.appendChild(self.pathList)
        self.entryFrame = SelectionContainer(self.nodeWidget, self.leafWidget)
        self.appendChild(self.entryFrame)
        self.entryFrame.selectionActivatedEvent.register(self)
        self._batchSize = 99
        self._currentCursor = {"node": None, "leaf": None}
        self._currentRequests = []
        self.rootNodeChangedEvent = EventDispatcher("rootNodeChanged")
        self.nodeChangedEvent = EventDispatcher("nodeChanged")

        assert selectMode in [
            None, "single", "multi", "single.leaf", "single.node",
            "multi.leaf", "multi.node"
        ]
        self.selectMode = selectMode

        if self.rootNode:
            self.reloadData()
            self.rebuildPath()
        else:
            NetworkService.request(self.module,
                                   "listRootNodes",
                                   successHandler=self.onSetDefaultRootNode)

        self.sinkEvent("onClick")

        # Proxy some events and functions of the original table
        for f in [
                "selectionChangedEvent", "selectionActivatedEvent",
                "cursorMovedEvent", "getCurrentSelection",
                "selectionReturnEvent"
        ]:
            setattr(self, f, getattr(self.entryFrame, f))

        self.actionBar.setActions(self.defaultActions + (
            ["select", "close"] if self.selectMode else []))

    def showErrorMsg(self, req=None, code=None):
        """
			Removes all currently visible elements and displays an error message
		"""
        self.actionBar["style"]["display"] = "none"
        self.entryFrame["style"]["display"] = "none"
        errorDiv = html5.Div()
        errorDiv["class"].append("error_msg")
        if code and (code == 401 or code == 403):
            txt = "Access denied!"
        else:
            txt = "An unknown error occurred!"
        errorDiv["class"].append("error_code_%s" % (code or 0))
        errorDiv.appendChild(html5.TextNode(txt))
        self.appendChild(errorDiv)

    def onAttach(self):
        super(TreeWidget, self).onAttach()
        NetworkService.registerChangeListener(self)

    def onDetach(self):
        super(TreeWidget, self).onDetach()
        NetworkService.removeChangeListener(self)

    def onDataChanged(self, module, **kwargs):
        if module != self.module:

            isRootNode = False
            for k, v in conf["modules"].items():
                if (k == module and v.get("handler") == "list"
                        and v.get("rootNodeOf") == self.module):
                    isRootNode = True
                    break

            if not isRootNode:
                return

        if "selectrootnode" in self.actionBar.widgets.keys():
            self.actionBar.widgets["selectrootnode"].update()

        self.reloadData()

    def onSelectionActivated(self, div, selection):
        if not selection:
            return

        item = selection[0]

        if item.skelType == "node":
            self.setNode(item.data["key"])

        elif item.skelType == "leaf" and "leaf" in (self.selectMode or ""):
            self.returnCurrentSelection()

    def activateCurrentSelection(self):
        return self.entryFrame.activateCurrentSelection()

    def returnCurrentSelection(self):
        conf["mainWindow"].removeWidget(self)
        return self.entryFrame.returnCurrentSelection()

    def onClick(self, event):
        super(TreeWidget, self).onClick(event)
        for c in self.pathList._children:
            # Test if the user clicked inside the path-list
            if html5.utils.doesEventHitWidgetOrParents(event, c):
                self.setNode(c.data["key"])
                return

    def onSetDefaultRootNode(self, req):
        data = NetworkService.decode(req)
        if len(data) > 0:
            self.setRootNode(data[0]["key"], self.node)

    def setRootNode(self, rootNode, node=None):
        self.rootNode = rootNode
        self.node = node or rootNode
        self.rootNodeChangedEvent.fire(rootNode)
        if node:
            self.nodeChangedEvent.fire(node)
        self.reloadData()
        self.rebuildPath()

    def setNode(self, node):
        self.node = node
        self.nodeChangedEvent.fire(node)
        self.reloadData()
        self.rebuildPath()

    def rebuildPath(self):
        """
			Rebuild the displayed path-list.
		"""
        self.pathList.removeAllChildren()

        NetworkService.request(self.module,
                               "view/node/%s" % self.node,
                               successHandler=self.onPathRequestSucceded)

    def onPathRequestSucceded(self, req):
        """
			Rebuild the displayed path-list according to request data
		"""
        answ = NetworkService.decode(req)
        skel = answ["values"]

        if skel["parentdir"] and skel["parentdir"] != skel["key"]:
            c = self.nodeWidget(self.module, skel, answ["structure"])

            NetworkService.request(self.module,
                                   "view/node/%s" % skel["parentdir"],
                                   successHandler=self.onPathRequestSucceded)

        else:
            c = self.nodeWidget(self.module, {
                "key": self.rootNode,
                "name": "root"
            }, [])
            c.addClass("is_rootnode")

        self.pathList.prependChild(c)

    def reloadData(self, paramsOverride=None):
        assert self.node is not None, "reloadData called while self.node is None"
        self.entryFrame.clear()
        self._currentRequests = []
        if paramsOverride:
            params = paramsOverride.copy()
        else:
            params = {"node": self.node}

        if "amount" not in params:
            params["amount"] = self._batchSize

        r = NetworkService.request(self.module,
                                   "list/node",
                                   params,
                                   successHandler=self.onRequestSucceded,
                                   failureHandler=self.showErrorMsg)
        r.reqType = "node"
        self._currentRequests.append(r)
        r = NetworkService.request(self.module,
                                   "list/leaf",
                                   params,
                                   successHandler=self.onRequestSucceded,
                                   failureHandler=self.showErrorMsg)
        r.reqType = "leaf"
        self._currentRequests.append(r)

        from handler.tree import TreeHandler  # import must be here, otherwise it throws an importError
        if isinstance(conf["mainWindow"].currentPane, TreeHandler):
            conf["theApp"].setPath(self.module + "/list/" + self.node)

    def onRequestSucceded(self, req):
        if not req in self._currentRequests:
            return

        self._currentRequests.remove(req)
        data = NetworkService.decode(req)

        for skel in data["skellist"]:
            if req.reqType == "node":
                n = self.nodeWidget(self.module, skel, data["structure"])
            else:
                n = self.leafWidget(self.module, skel, data["structure"])

            self.entryFrame.appendChild(n)

        self.entryFrame.sortChildren(self.getChildKey)

        if "cursor" in data.keys() and len(
                data["skellist"]) == req.params["amount"]:
            self._currentCursor[req.reqType] = data["cursor"]

            req.params["cursor"] = data["cursor"]
            r = NetworkService.request(self.module,
                                       "list/%s" % req.reqType,
                                       req.params,
                                       successHandler=self.onRequestSucceded,
                                       failureHandler=self.showErrorMsg)
            r.reqType = req.reqType
            self._currentRequests.append(r)
        else:
            self._currentCursor[req.reqType] = None

        self.actionBar.resetLoadingState()

    def getChildKey(self, widget):
        """
			Derives a string used to sort the entries in our entryframe
		"""
        name = (widget.data.get("name") or "").lower()

        if isinstance(widget, self.nodeWidget):
            return "0-%s" % name
        elif isinstance(widget, self.leafWidget):
            return "1-%s" % name
        else:
            return "2-"

    @staticmethod
    def canHandle(moduleName, moduleInfo):
        return moduleInfo["handler"].startswith("tree.")

    @staticmethod
    def render(moduleName, adminInfo, context):
        rootNode = context.get(conf["vi.context.prefix"] +
                               "rootNode") if context else None
        return TreeWidget(module=moduleName,
                          rootNode=rootNode,
                          context=context)
Пример #4
0
class EditWidget(html5.Div):
	appList = "list"
	appHierarchy = "hierarchy"
	appTree = "tree"
	appSingleton = "singleton"
	__editIdx_ = 0 #Internal counter to ensure unique ids

	def __init__(self, module, applicationType, key=0, node=None, skelType=None, clone=False,
	                hashArgs=None, context=None, logaction = "Entry saved!", *args, **kwargs):
		"""
			Initialize a new Edit or Add-Widget for the given module.
			:param module: Name of the module
			:type module: str
			:param applicationType: Defines for what application this Add / Edit should be created. This hides additional complexity introduced by the hierarchy / tree-application
			:type applicationType: Any of EditWidget.appList, EditWidget.appHierarchy, EditWidget.appTree or EditWidget.appSingleton
			:param id: ID of the entry. If none, it will add a new Entry.
			:type id: Number
			:param rootNode: If applicationType==EditWidget.appHierarchy, the new entry will be added under this node, if applicationType==EditWidget,appTree the final node is derived from this and the path-parameter.
			Has no effect if applicationType is not appHierarchy or appTree or if an id have been set.
			:type rootNode: str
			:param path: Specifies the path from the rootNode for new entries in a treeApplication
			:type path: str
			:param clone: If true, it will load the values from the given id, but will save a new entry (i.e. allows "cloning" an existing entry)
			:type clone: bool
			:param hashArgs: Dictionary of parameters (usually supplied by the window.hash property) which should prefill values.
			:type hashArgs: dict
		"""
		if not module in conf["modules"].keys():
			conf["mainWindow"].log("error", translate("The module '{module}' does not exist.", module=module))
			assert module in conf["modules"].keys()

		super(EditWidget, self ).__init__(*args, **kwargs)
		self.module = module

		# A Bunch of santy-checks, as there is a great chance to mess around with this widget
		assert applicationType in [ EditWidget.appList, EditWidget.appHierarchy, EditWidget.appTree, EditWidget.appSingleton ] #Invalid Application-Type?

		if applicationType==EditWidget.appHierarchy or applicationType==EditWidget.appTree:
			assert key is not None or node is not None #Need either an id or an node

		if clone:
			assert key is not None #Need an id if we should clone an entry
			assert not applicationType==EditWidget.appSingleton # We cant clone a singleton
			if applicationType==EditWidget.appHierarchy or applicationType==EditWidget.appTree:
				assert node is not None #We still need a rootNode for cloning
			if applicationType==EditWidget.appTree:
				assert node is not None #We still need a path for cloning #FIXME

			self.clone_of = key
		else:
			self.clone_of = None

		# End santy-checks
		self.editIdx = EditWidget.__editIdx_ #Internal counter to ensure unique ids
		EditWidget.__editIdx_ += 1
		self.applicationType = applicationType
		self.key = key
		self.mode = "edit" if self.key or applicationType == EditWidget.appSingleton else "add"
		self.modified = False
		self.node = node
		self.skelType = skelType
		self.clone = clone
		self.bones = {}
		self.closeOnSuccess = False
		self.logaction = logaction
		self.sinkEvent("onChange")

		self.context = context
		self.views = {}

		self._lastData = {} #Dict of structure and values received

		if hashArgs:
			self._hashArgs = parseHashParameters(hashArgs)
		else:
			self._hashArgs = None

		self.editTaskID = None
		self.wasInitialRequest = True #Wherever the last request attempted to save data or just fetched the form

		# Action bar
		self.actionbar = ActionBar(self.module, self.applicationType, (self.mode if not clone else "clone"))
		self.appendChild(self.actionbar)

		editActions = []

		if self.mode == "edit":
			editActions.append("refresh")

		if module in conf["modules"] and conf["modules"][module]:
			editActions.extend(conf["modules"][module].get("editActions", []))

		if applicationType == EditWidget.appSingleton:
			self.actionbar.setActions(["save.singleton"] + editActions)
		else:
			self.actionbar.setActions(["save.close", "save.continue"] + editActions)

		# Set path
		if applicationType == EditWidget.appSingleton:
			conf["theApp"].setPath(module + "/" + self.mode)
		elif self.mode == "edit":
			conf["theApp"].setPath(module + "/" + (self.mode if not clone else "clone") + "/" + self.key)
		else:
			conf["theApp"].setPath(module + "/" + self.mode)

		# Input form
		self.form = html5.Form()
		self.appendChild(self.form)

		# Engage
		self.reloadData()

	def onDetach(self):
		utils.setPreventUnloading(False)
		super(EditWidget, self).onDetach()

	def onAttach(self):
		super(EditWidget, self).onAttach()
		utils.setPreventUnloading(True)

	def performLogics(self):
		fields = self.serializeForDocument()

		for key, desc in self.dataCache["structure"]:
			if desc.get("params") and desc["params"]:
				for event in ["logic.visibleIf", "logic.readonlyIf", "logic.evaluate"]: #add more here!
					logic = desc["params"].get(event)

					if not logic:
						continue

					# Compile logic at first run
					if isinstance(logic, str):
						desc["params"][event] = conf["logics"].compile(logic)
						if desc["params"][event] is None:
							alert("ViUR logics: Parse error in >%s<" % logic)
							continue

						logic = desc["params"][event]

					res = conf["logics"].execute(logic, fields)

					if event == "logic.evaluate":
						self.bones[key].unserialize({key: res})
					elif res:
						if event == "logic.visibleIf":
							self.containers[key].show()
						elif event == "logic.readonlyIf":
							self.containers[key].disable()

						# add more here...
					else:
						if event == "logic.visibleIf":
							self.containers[key].hide()
						elif event == "logic.readonlyIf":
							self.containers[key].enable()
						# add more here...

	def onChange(self, event):
		self.modified = True
		DeferredCall(self.performLogics)

	def onBoneChange(self, bone):
		self.modified = True
		DeferredCall(self.performLogics)

	def showErrorMsg(self, req=None, code=None):
		"""
			Removes all currently visible elements and displays an error message
		"""
		try:
			print(req.result)
			print(NetworkService.decode(req))
		except:
			pass

		if code and (code==401 or code==403):
			txt = translate("Access denied!")
		else:
			txt = translate("An error occurred: {code}", code=code or 0)

		conf["mainWindow"].log("error", txt)

		if self.wasInitialRequest:
			conf["mainWindow"].removeWidget(self)

	def reloadData(self):
		self.save({})

	def save(self, data):
		"""
			Creates the actual NetworkService request used to transmit our data.
			If data is None, it fetches a clean add/edit form.

			:param data: The values to transmit or None to fetch a new, clean add/edit form.
			:type data: dict or None
		"""
		self.wasInitialRequest = not len(data) > 0

		if self.context:
			# Data takes precedence over context.
			ndata = self.context.copy()
			ndata.update(data.copy())
			data = ndata

		if self.module=="_tasks":
			NetworkService.request(None, "/vi/%s/execute/%s" % (self.module, self.key), data,
			                        secure=not self.wasInitialRequest,
			                        successHandler=self.setData,
			                        failureHandler=self.showErrorMsg)

		elif self.applicationType == EditWidget.appList: ## Application: List
			if self.key and (not self.clone or self.wasInitialRequest):
				NetworkService.request(self.module, "edit/%s" % self.key, data,
				                       secure=not self.wasInitialRequest,
				                       successHandler=self.setData,
				                       failureHandler=self.showErrorMsg)
			else:
				NetworkService.request(self.module, "add", data,
				                       secure=not self.wasInitialRequest,
				                       successHandler=self.setData,
				                       failureHandler=self.showErrorMsg )

		elif self.applicationType == EditWidget.appHierarchy: ## Application: Hierarchy
			if self.key and (not self.clone or self.wasInitialRequest):
				NetworkService.request(self.module, "edit/%s" % self.key, data,
				                       secure=not self.wasInitialRequest,
				                       successHandler=self.setData,
				                       failureHandler=self.showErrorMsg)
			else:
				NetworkService.request(self.module, "add/%s" % self.node, data,
				                       secure=not self.wasInitialRequest,
				                       successHandler=self.setData,
				                       failureHandler=self.showErrorMsg)

		elif self.applicationType == EditWidget.appTree: ## Application: Tree
			if self.key and not self.clone:
				NetworkService.request(self.module, "edit/%s/%s" % (self.skelType, self.key), data,
				                       secure=not self.wasInitialRequest,
				                       successHandler=self.setData,
				                       failureHandler=self.showErrorMsg)
			else:
				NetworkService.request(self.module, "add/%s/%s" % (self.skelType, self.node), data,
				                       secure=not self.wasInitialRequest,
				                       successHandler=self.setData,
				                       failureHandler=self.showErrorMsg)

		elif self.applicationType == EditWidget.appSingleton: ## Application: Singleton
			NetworkService.request(self.module, "edit", data,
			                       secure=not self.wasInitialRequest,
			                       successHandler=self.setData,
			                       failureHandler=self.showErrorMsg)
		else:
			raise NotImplementedError() #Should never reach this

	def clear(self):
		"""
			Removes all visible bones/forms/fieldsets.
		"""
		for c in self.form._children[ : ]:
			self.form.removeChild( c )

	def closeOrContinue(self, sender=None ):
		NetworkService.notifyChange(self.module, key=self.key, action=self.mode)

		if self.closeOnSuccess:
			if self.module == "_tasks":
				self.parent().close()
				return

			conf["mainWindow"].removeWidget(self)
			return

		self.clear()
		self.bones = {}

		if self.mode == "add":
			self.key = 0

		self.reloadData()

	def doCloneHierarchy(self, sender=None ):
		if self.applicationType == EditWidget.appHierarchy:
			NetworkService.request( self.module, "clone",
		                            { "fromRepo" : self.node, "toRepo" : self.node,
		                              "fromParent" : self.clone_of, "toParent" : self.key },
		                                secure=True, successHandler=self.cloneComplete )
		else:
			NetworkService.request( conf[ "modules" ][ self.module ][ "rootNodeOf" ], "clone",
		                            { "fromRepo" : self.clone_of, "toRepo" : self.key },
		                                secure=True, successHandler=self.cloneComplete )

	def cloneComplete(self, request):
		logDiv = html5.Div()
		logDiv["class"].append("msg")
		spanMsg = html5.Span()
		spanMsg.appendChild( html5.TextNode( translate( u"The hierarchy will be cloned in the background." ) ) )
		spanMsg["class"].append("msgspan")
		logDiv.appendChild(spanMsg)

		conf["mainWindow"].log("success",logDiv)
		self.closeOrContinue()

	def setData(self, request=None, data=None, ignoreMissing=False, askHierarchyCloning=True):
		"""
		Rebuilds the UI according to the skeleton received from server

		:param request: A finished NetworkService request
		:type request: NetworkService
		:type data: dict
		:param data: The data received
		"""
		assert (request or data)

		if request:
			data = NetworkService.decode(request)

		if "action" in data and (data["action"] == "addSuccess" or data["action"] == "editSuccess"):
			self.modified = False

			logDiv = html5.Div()
			logDiv["class"].append("msg")
			spanMsg = html5.Span()

			spanMsg.appendChild( html5.TextNode( translate( self.logaction ) ) )
			spanMsg["class"].append("msgspan")
			logDiv.appendChild(spanMsg)

			if self.module in conf["modules"].keys():
				spanMsg = html5.Span()
				if self.module.startswith( "_" ):
					spanMsg.appendChild( html5.TextNode( self.key ) )
				else:
					spanMsg.appendChild( html5.TextNode( conf["modules"][self.module]["name"] ))
				spanMsg["class"].append("modulespan")
				logDiv.appendChild(spanMsg)

			if "values" in data.keys() and "name" in data["values"].keys():
				spanMsg = html5.Span()

				name = data["values"].get("name") or data["values"].get("key", "")
				if isinstance(name, dict):
					if conf["currentlanguage"] in name.keys():
						name = name[conf["currentlanguage"]]
					else:
						name = name.values()

				if isinstance(name, list):
					name = ", ".join(name)

				spanMsg.appendChild(html5.TextNode(str(html5.utils.unescape(name))))
				spanMsg["class"].append("namespan")
				logDiv.appendChild(spanMsg)

			try:
				self.key = data["values"]["key"]
			except:
				self.key = None

			conf["mainWindow"].log("success",logDiv)

			if askHierarchyCloning and self.clone:
				# for lists, which are rootNode entries of hierarchies, ask to clone entire hierarchy
				if self.applicationType == EditWidget.appList and "rootNodeOf" in conf[ "modules" ][ self.module ]:
					YesNoDialog( translate( u"Do you want to clone the entire hierarchy?" ),
				                    yesCallback=self.doCloneHierarchy, noCallback=self.closeOrContinue )
					return
				# for cloning within a hierarchy, ask for cloning all subentries.
				elif self.applicationType == EditWidget.appHierarchy:
					YesNoDialog( translate( u"Do you want to clone all subentries of this item?" ),
				                    yesCallback=self.doCloneHierarchy, noCallback=self.closeOrContinue )
					return

			self.closeOrContinue()
			return

		#Clear the UI
		self.clear()
		self.bones = {}
		self.views = {}
		self.containers = {}
		self.actionbar.resetLoadingState()
		self.dataCache = data
		self.modified = False

		tmpDict = {k: v for k, v in data["structure"]}
		fieldSets = {}
		firstCat = None
		currRow = 0
		hasMissing = False
		defaultCat = conf["modules"][self.module].get("visibleName", self.module)

		contextVariable = conf["modules"][self.module].get("editContext")
		if self.mode == "edit" and contextVariable:
			if not self.context:
				self.context = {}

			if "=" in contextVariable:
				contextVariable, contextKey = contextVariable.split("=", 1)
			else:
				contextKey = "key"

			self.context.update({
				contextVariable: data["values"].get(contextKey)
			})

		for key, bone in data["structure"]:

			cat = defaultCat #meow!

			if ("params" in bone.keys()
			    and isinstance(bone["params"], dict)
			    and "category" in bone["params"].keys()):
				cat = bone["params"]["category"]

			if not cat in fieldSets.keys():
				fieldSets[cat] = EditWidgetFieldset(cat)

			wdgGen = editBoneSelector.select(self.module, key, tmpDict)
			widget = wdgGen.fromSkelStructure(self.module, key, tmpDict)
			widget["id"] = "vi_%s_%s_%s_%s_bn_%s" % (self.editIdx, self.module, self.mode, cat, key)

			if "setContext" in dir(widget) and callable(widget.setContext):
				widget.setContext(self.context)

			if "changeEvent" in dir(widget):
				widget.changeEvent.register(self)

			descrLbl = html5.Label(key if conf["showBoneNames"] else bone.get("descr", key))
			descrLbl["class"].append(key)
			descrLbl["class"].append(bone["type"].replace(".","_"))

			# Elements
			if ("params" in bone.keys()
			    and isinstance(bone["params"], dict)
			    and "elements.source" in bone["params"].keys()):
				descrLbl.addClass("elements-%s" % bone["params"]["elements.source"])

			descrLbl["for"] = "vi_%s_%s_%s_%s_bn_%s" % (self.editIdx, self.module, self.mode, cat, key)

			if bone["required"] or (bone.get("unique") and bone["error"]):
				descrLbl["class"].append("is_required")

				if bone["error"] is not None:
					descrLbl["class"].append("is_invalid")
					descrLbl["title"] = bone["error"]
					fieldSets[ cat ]["class"].append("is_incomplete")

					# Put info into message center
					conf["mainWindow"].log("info", "%s: %s" % (bone.get("descr", key), translate(bone["error"])))

					hasMissing = True

				elif bone["error"] is None and not self.wasInitialRequest:
					descrLbl["class"].append("is_valid")

			if isinstance(bone["error"], dict):
				widget.setExtendedErrorInformation(bone["error"])

			containerDiv = html5.Div()
			containerDiv.appendChild(descrLbl)
			containerDiv.appendChild(widget)

			if ("params" in bone.keys()
			    and isinstance(bone["params"], dict)
			    and "tooltip" in bone["params"].keys()):
				containerDiv.appendChild(ToolTip(longText=bone["params"]["tooltip"]))

			fieldSets[cat]._section.appendChild(containerDiv)
			containerDiv.addClass("bone", "bone_%s" % key, bone["type"].replace(".","_"))

			if "." in bone["type"]:
				for t in bone["type"].split("."):
					containerDiv["class"].append(t)

			currRow += 1
			self.bones[key] = widget
			self.containers[key] = containerDiv

			#Hide invisible bones or logic-flavored bones with their default desire
			if not bone["visible"] or (bone["params"] and bone["params"].get("logic.visibleIf")):
				self.containers[key].hide()
			elif bone["visible"] and not firstCat:
				firstCat = fieldSets[cat]

			# NO elif!
			if bone["params"] and bone["params"].get("logic.readonlyIf"):
				self.containers[key].disable()

		# Hide all fieldSets where all fields are invisible
		for fs in fieldSets.values():
			fs.checkVisibility()

		# Show default category
		if firstCat:
			firstCat.removeClass("inactive")
			firstCat.addClass("active")

		tmpList = [(k,v) for (k,v) in fieldSets.items()]
		tmpList.sort(key=lambda x:x[0])

		for k, v in tmpList:
			self.form.appendChild( v )
			v._section = None

		# Views
		views = conf["modules"][self.module].get("editViews")
		if self.mode == "edit" and isinstance(views, list):
			for view in views:
				vmodule = view.get("module")
				vvariable = view.get("context")
				vclass = view.get("class")
				vtitle = view.get("title")
				vcolumns = view.get("columns")
				vfilter = view.get("filter")
				vactions = view.get("actions")

				if not vmodule:
					print("Misconfiured view: %s" % view)
					continue

				if vmodule not in conf["modules"]:
					print("Module '%s' is not described." % vmodule)
					continue

				vdescr = conf["modules"][vmodule]

				fs = EditWidgetFieldset(vmodule, vtitle or vdescr.get("name", vmodule))
				fs.addClass("editview", "inactive")

				if vclass:
					fs.addClass(*vclass)

				fieldSets[vmodule] = EditWidgetFieldset(vmodule, vtitle or vdescr.get("name", vmodule))

				if vvariable:
					context = self.context.copy() if self.context else {}

					if "=" in vvariable:
						vkey, vvalue = vvariable.split("=", 1)
						if vvalue[0] == "$":
							vvalue = data["values"].get(vvalue[1:])
					else:
						vkey = vvariable
						vvalue = data["values"].get("key")

					context[vkey] = vvalue

				else:
					context = self.context

				self.views[vmodule] = ListWidget(
					vmodule,
					filter=vfilter or vdescr.get("filter", {}),
					columns=vcolumns or vdescr.get("columns"),
					context=context,
					actions=vactions
				)

				fs._section.appendChild(self.views[vmodule])
				self.form.appendChild(fs)

		self.unserialize(data["values"])

		if self._hashArgs: #Apply the default values (if any)
			self.unserialize(self._hashArgs)
			self._hashArgs = None

		self._lastData = data

		if hasMissing and not self.wasInitialRequest:
			conf["mainWindow"].log("warning",translate("Could not save entry!"))

		DeferredCall(self.performLogics)

	def unserialize(self, data = None):
		"""
			Applies the actual data to the bones.
		"""
		for bone in self.bones.values():
			if "setContext" in dir(bone) and callable(bone.setContext):
				bone.setContext(self.context)

			if data is not None:
				bone.unserialize(data)

	def serializeForPost(self, validityCheck = False):
		res = {}

		for key, bone in self.bones.items():
			if key == "key":
				continue #ignore the key, it is stored in self.key!

			try:
				res.update(bone.serializeForPost())
			except InvalidBoneValueException:
				if validityCheck:
					# Fixme: Bad hack..
					lbl = bone.parent()._children[0]
					if "is_valid" in lbl["class"]:
						lbl["class"].remove("is_valid")
					lbl["class"].append("is_invalid")
					self.actionbar.resetLoadingState()
					return None

		return res

	def serializeForDocument(self):
		res = self._lastData.get("values", {})

		for key, bone in self.bones.items():
			try:
				res.update(bone.serializeForDocument())
			except InvalidBoneValueException as e:
				res[key] = str(e)

		return res

	def doSave( self, closeOnSuccess=False, *args, **kwargs ):
		"""
			Starts serializing and transmitting our values to the server.
		"""
		self.closeOnSuccess = closeOnSuccess

		res = self.serializeForPost(True)
		if res is None:
			return None

		self.save(res)
Пример #5
0
class HierarchyWidget(html5.Div):
	"""
		Displays a hierarchy where entries are direct children of each other.
		(There's only one type on entries in a HierarchyApplication. If you need to
		differentiate between nodes/leafs use a TreeApplication instead)
	"""

	def __init__(self, module, rootNode=None, selectMode=None, context=None, *args, **kwargs):
		"""
			:param module: Name of the module we shall handle. Must be a hierarchy application!
			:type module: str
			:param rootNode: The repository we shall display. If none, we try to select one.
			:type rootNode: str or None
		"""
		super( HierarchyWidget, self ).__init__( )
		self.module = module
		self.rootNode = rootNode
		self.actionBar = ActionBar(module, "hierarchy")
		self.appendChild( self.actionBar )
		self.entryFrame = html5.Ol()
		self.entryFrame["class"].append("hierarchy")
		self.appendChild( self.entryFrame )
		self.selectionChangedEvent = EventDispatcher("selectionChanged")
		self.selectionActivatedEvent = EventDispatcher("selectionActivated")
		self.rootNodeChangedEvent = EventDispatcher("rootNodeChanged")
		self._currentCursor = None
		self._currentRequests = []
		self.addClass("supports_drop")

		assert selectMode in [None, "single", "multi"]
		self.selectMode = selectMode

		self._expandedNodes = []
		self.context = context

		if self.rootNode:
			self.reloadData()
		else:
			NetworkService.request(self.module, "listRootNodes",
			                       self.context or {},
			                       successHandler=self.onSetDefaultRootNode,
			                       failureHandler=self.showErrorMsg )

		self.path = []
		self.sinkEvent("onClick", "onDblClick")

		##Proxy some events and functions of the original table
		#for f in ["selectionChangedEvent","selectionActivatedEvent","cursorMovedEvent","getCurrentSelection"]:
		#	setattr( self, f, getattr(self.table,f))
		self.actionBar.setActions(["selectrootnode","add","edit","clone","delete"]+(["select","close"] if self.selectMode else [])+["reload"])
		self.sinkEvent("onDrop","onDragOver")


	def showErrorMsg(self, req=None, code=None):
		"""
			Removes all currently visible elements and displayes an error message
		"""
		self.actionBar["style"]["display"] = "none"
		self.entryFrame["style"]["display"] = "none"
		errorDiv = html5.Div()
		errorDiv["class"].append("error_msg")
		if code and (code==401 or code==403):
			txt = translate("Access denied!")
		else:
			txt = translate("An unknown error occurred!")
		errorDiv["class"].append("error_code_%s" % (code or 0))
		errorDiv.appendChild( html5.TextNode( txt ) )
		self.appendChild( errorDiv )

	def onDataChanged(self, module, **kwargs):

		if module != self.module:

			isRootNode = False
			for k, v in conf[ "modules" ].items():
				if (k == module
				    and v.get("handler") == "list"
				    and v.get("rootNodeOf") == self.module):

					isRootNode = True
					break

			if not isRootNode:
				return

		self.actionBar.widgets["selectrootnode"].update()
		self.reloadData()

	def onAttach(self):
		super( HierarchyWidget, self ).onAttach()
		NetworkService.registerChangeListener( self )

	def onDetach(self):
		super( HierarchyWidget, self ).onDetach()
		NetworkService.removeChangeListener( self )

	def itemForEvent(self,event, elem=None):
		"""
			Tries to map an event to one of our HierarchyItems.
			Returns the item actually clicked, not its top-level parent.
		"""
		if elem is None:
			elem = self.entryFrame
		for child in elem._children:
			if child.element==event.target:
				if isinstance( child, HierarchyItem ):
					# User clicked directly on one HierarchyItem
					return( child )
				else:
					# User clicked somewhere INTO one HierarchyItem
					# Return a marker to the outer recursion that this is the correct HierarchyItem
					return( False )
			tmp = self.itemForEvent( event, child )
			if tmp is False:
				if isinstance(child, HierarchyItem):
					return( child )
				else:
					return( False )
			elif tmp is not None:
				return( tmp )
		return( None )

	def itemForKey(self, key, elem=None ):
		"""
			Returns the HierarchyWidget displaying the entry with the given key.
			:param key: The key (id) of the item.
			:type key: str
			:returns: HierarchyItem
		"""
		if elem is None:
			elem = self.entryFrame
		for child in elem._children:
			if child.data["key"]==key:
				return( child )
			tmp = self.itemForKey( key, child.ol )
			if tmp is not None:
				return( tmp )
		return( None )

	def onClick(self, event):
		item = self.itemForEvent(event)

		if item is None:
			return

		if html5.utils.doesEventHitWidgetOrChildren(event, item.expandLink):
			item.toggleExpand()

			if not item.isLoaded:
				item.isLoaded = True
				self.loadNode(item.data["key"])

		else:
			self.setCurrentItem( item )
			self.selectionChangedEvent.fire( self, item )

	def onDblClick(self, event):
		item = self.itemForEvent( event )
		if item is None:
			return
		self.setCurrentItem( item )
		self.selectionActivatedEvent.fire( self, [item] )
		if self.selectMode:
			conf["mainWindow"].removeWidget(self)

	def setCurrentItem(self, item):
		if self._currentCursor:
			self._currentCursor["class"].remove("is_focused")
		item["class"].append("is_focused")
		self._currentCursor = item

	def onSetDefaultRootNode(self, req):
		"""
			We requested the list of rootNodes for that module and that
			request just finished. Parse the respone and set our rootNode
			to the first rootNode received.
		"""
		data = NetworkService.decode( req )
		if len(data)>0:
			self.setRootNode( data[0]["key"])

	def setRootNode(self, rootNode):
		"""
			Set the currently displayed hierarchy to 'rootNode'.
			:param rootNode: Key of the rootNode which children we shall display
			:type rootNode: str
		"""
		self.rootNode = rootNode
		self._currentCursor = None
		self.rootNodeChangedEvent.fire(rootNode)
		self.reloadData()

	def reloadData(self):
		"""
			Reload the data were displaying.
		"""
		def collectExpandedNodes( currNode ):
			res = []
			for c in currNode._children[:]:
				if isinstance( c, HierarchyItem ):
					if c.isExpanded:
						res.append( c.data["key"] )
					res.extend( collectExpandedNodes(c.ol) )
			return( res )
		self._expandedNodes = collectExpandedNodes( self.entryFrame )
		self._currentRequests = []
		for c in self.entryFrame._children[:]:
			self.entryFrame.removeChild(c)
		self.loadNode( self.rootNode )

	def loadNode(self, node, cursor = None):
		"""
			Fetch the (direct) children of the given node.
			Once the list is received, append them to their parent node.
			:param node: Key of the node to fetch
			:type node: str
		"""
		params = {
			"parent": node,
		    "orderby": "sortindex"
		}

		if cursor:
			params.update({"cursor": cursor})

		if self.context:
			params.update(self.context)

		r = NetworkService.request(self.module, "list",
		                            params,
		                            successHandler=self.onRequestSucceded,
		                            failureHandler=self.showErrorMsg)
		r.node = node
		self._currentRequests.append(r)

	def onRequestSucceded(self, req):
		"""
			The NetworkRequest for a (sub)node finished.
			Create a new HierarchyItem for each entry received and add them to our view
		"""
		if not req in self._currentRequests:
			#Prevent inserting old (stale) data
			self.actionBar.resetLoadingState()
			return

		self._currentRequests.remove(req)
		data = NetworkService.decode(req)

		if req.node == self.rootNode:
			ol = self.entryFrame
		else:
			tmp = self.itemForKey(req.node)
			ol = tmp.ol
			assert ol is not None

		for skel in data["skellist"]:
			hi = HierarchyItem( self.module, skel, data["structure"] )
			ol.appendChild( hi )
			if hi.data["key"] in self._expandedNodes:
				hi.toggleExpand()
				if not hi.isLoaded:
					hi.isLoaded = True
					self.loadNode(hi.data["key"])

		if not ol._children and ol != self.entryFrame:
			ol.parent()["class"].append("has_no_childs")

		if data["skellist"] and data["cursor"]:
			self.loadNode(req.node, data["cursor"])

		self.actionBar.resetLoadingState()

	def getCurrentSelection(self):
		"""
			Returns the list of entries currently selected.
			:returns: list of dict
		"""
		if self._currentCursor is not None:
			return( [ self._currentCursor.data ] )
		return( [] )

	def onDrop(self, event):
		"""
			We got a drop event. Make that item a direct child of our rootNode
		"""
		srcKey = event.dataTransfer.getData("Text")
		NetworkService.request(self.module,"reparent",{"item":srcKey,"dest":self.rootNode}, secure=True, modifies=True )
		event.stopPropagation()

	def onDragOver(self, event):
		"""
			Allow dropping children on the rootNode
		"""
		event.preventDefault()
		event.stopPropagation()

	def activateCurrentSelection(self):
		if self._currentCursor:
			self.selectionActivatedEvent.fire( self, [self._currentCursor] )
			conf["mainWindow"].removeWidget(self)

	@staticmethod
	def canHandle(moduleName, moduleInfo):
		return moduleInfo["handler"] == "hierarchy" or moduleInfo["handler"].startswith("hierarchy.")

	@staticmethod
	def render(moduleName, adminInfo, context):
		rootNode = context.get(conf["vi.context.prefix"] + "rootNode") if context else None
		return HierarchyWidget(module=moduleName, rootNode=rootNode, context=context)
Пример #6
0
class Wysiwyg(html5.Div):
    def __init__(self,
                 editHtml,
                 actionBarHint=translate("Text Editor"),
                 *args,
                 **kwargs):
        super(Wysiwyg, self).__init__(*args, **kwargs)
        self.cursorMovedEvent = EventDispatcher("cursorMoved")
        self.saveTextEvent = EventDispatcher("saveText")
        self.abortTextEvent = EventDispatcher("abortText")
        self.textActions = ["style.text.bold",
                            "style.text.italic"]+\
                           ["style.text.h%s" % x for x in range(0,4)]+\
                           ["text.removeformat",
             "style.text.justifyCenter",
             "style.text.justifyLeft",
             "style.text.justifyRight",
                         "style.text.blockquote",
                         "text.orderedList",
                         "text.unorderedList",
                         "text.indent",
                         "text.outdent",
                         "text.image",
                         "text.link",
                         "text.table",
                         "text.flipView",
                         "text.undo",
                         "text.redo",
                         "text.abort",
                         "text.save"]

        #self["type"] = "text"
        self.actionbar = ActionBar(None, None, actionBarHint)
        self.isWysiwygMode = True
        self.discardNextClickEvent = False
        self.appendChild(self.actionbar)
        self.tableDiv = html5.Div()
        self.tableDiv["class"].append("tableeditor")
        self.appendChild(self.tableDiv)
        for c in [
                TableInsertRowBeforeAction, TableInsertRowAfterAction,
                TableInsertColBeforeAction, TableInsertColAfterAction,
                TableRemoveRowAction, TableRemoveColAction
        ]:
            self.tableDiv.appendChild(c())
        self.tableDiv["style"]["display"] = "none"
        self.linkEditor = LinkEditor()
        self.appendChild(self.linkEditor)
        self.imgEditor = ImageEditor()
        self.appendChild(self.imgEditor)

        self.editor = Editor(editHtml)

        self.appendChild(self.editor)
        self.actionbar.setActions(self.textActions)
        #btn = html5.ext.Button("Apply", self.saveText)
        #btn["class"].append("icon apply")
        #self.appendChild( btn )
        self.currentImage = None
        self.cursorImage = None
        self.lastMousePos = None
        self.sinkEvent("onMouseDown", "onMouseUp", "onMouseMove", "onClick")

    def flipView(self, *args, **kwargs):
        htmlStr = self.editor.element.innerHTML
        if self.isWysiwygMode:
            self.imgEditor.doClose()
            self.linkEditor.doClose()
            self.tableDiv["style"]["display"] = None
            outStr = ""
            indent = 0
            indestStr = "&nbsp;&nbsp;&nbsp;"
            inStr = htmlStr.replace("&", "&amp;").replace("<", "&lt;").replace(
                ">", "&gt;")
            while inStr:
                if inStr.startswith("&lt;div&gt;"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                    indent += 1
                elif inStr.startswith("&lt;/div&gt;"):
                    indent -= 1
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;br"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;table"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                    indent += 1
                elif inStr.startswith("&lt;/table"):
                    indent -= 1
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;tr"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                    indent += 1
                elif inStr.startswith("&lt;/tr"):
                    indent -= 1
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;td"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;th&gt;"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;thead&gt;"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                    indent += 1
                elif inStr.startswith("&lt;/thead&gt;"):
                    indent -= 1
                    outStr += "<br>"
                    outStr += indestStr * indent
                elif inStr.startswith("&lt;tbody&gt;"):
                    outStr += "<br>"
                    outStr += indestStr * indent
                    indent += 1
                elif inStr.startswith("&lt;/tbody&gt;"):
                    indent -= 1
                    outStr += "<br>"
                    outStr += indestStr * indent
                outStr += inStr[0]
                inStr = inStr[1:]
            self.editor.element.innerHTML = outStr
            self.actionbar.setActions(["text.flipView"])
        else:
            htmlStr = re.sub(r'<[^>]*?>', '', htmlStr)
            htmlStr = htmlStr.replace("&nbsp;", "").replace("&nbsp;", "")
            self.editor.element.innerHTML = htmlStr.replace(
                "&lt;", "<").replace("&gt;", ">").replace("&amp;", "&")
            self.actionbar.setActions(self.textActions)

        self.isWysiwygMode = not self.isWysiwygMode
        return self.isWysiwygMode

    def saveText(self, *args, **kwargs):
        self.saveTextEvent.fire(self, self.editor.element.innerHTML)

    def abortText(self, *args, **kwargs):
        self.abortTextEvent.fire(self)

    def onMouseDown(self, event):
        self.lastMousePos = None
        if event.target.tagName == "IMG":
            offsetLeft = event.pageX - event.target.offsetLeft
            offsetTop = event.pageY - event.target.offsetTop
            if event.target.offsetParent is not None:
                offsetLeft -= event.target.offsetParent.offsetLeft
                offsetTop -= event.target.offsetParent.offsetTop
            if offsetLeft > 0.8 * event.target.clientWidth and offsetTop > 0.8 * event.target.clientHeight:
                self.currentImage = event.target
            self.imgEditor.doOpen(event.target)
            self.discardNextClickEvent = True
            event.preventDefault()
            event.stopPropagation()
        else:
            self.currentImage = None
            super(Wysiwyg, self).onMouseDown(event)

        node = eval("window.top.getSelection().anchorNode")

        while node and node != self.editor.element:
            #FIXME.. emit cursormoved event
            node = node.parentNode

    def onMouseUp(self, event):
        self.currentImage = None
        self.lastMousePos = None
        super(Wysiwyg, self).onMouseUp(event)

    def onMouseMove(self, event):
        if event.target.tagName == "IMG":
            offsetLeft = event.pageX - event.target.offsetLeft
            offsetTop = event.pageY - event.target.offsetTop
            if event.target.offsetParent is not None:
                offsetLeft -= event.target.offsetParent.offsetLeft
                offsetTop -= event.target.offsetParent.offsetTop
            if offsetLeft > 0.8 * event.target.clientWidth and offsetTop > 0.8 * event.target.clientHeight:
                self.cursorImage = event.target
                self.cursorImage.style.cursor = "se-resize"
            else:
                if self.cursorImage is not None:
                    self.cursorImage.style.cursor = "default"
                    self.cursorImage = None
        elif self.cursorImage is not None:
            self.cursorImage.style.cursor = "default"
            self.cursorImage = None
        if self.currentImage is not None and event.target.tagName == "IMG" and self.currentImage == event.target:
            if self.lastMousePos is None:
                self.lastMousePos = (event.x, event.y)
                return
            x, y = self.lastMousePos
            self.lastMousePos = (event.x, event.y)
            event.target.width = event.target.clientWidth - (x - event.x)
            event.target.height = event.target.clientHeight - (y - event.y)
            event.preventDefault()
            event.stopPropagation()
        else:
            self.lastMousePos = None
            self.currentImage = None
            super(Wysiwyg, self).onMouseMove(event)

    def onClick(self, event):
        if self.discardNextClickEvent:
            self.discardNextClickEvent = False
            return

        super(Wysiwyg, self).onClick(event)
        domWdg = event.target
        isEditorTarget = False

        while domWdg:
            if domWdg == self.editor.element:
                isEditorTarget = True
                break
            domWdg = domWdg.parentNode

        if not isEditorTarget:
            return

        node = eval("window.top.getSelection().anchorNode")
        nodeStack = []
        i = 10

        #Try to extract the relevant nodes from the dom
        while i > 0 and node and node != self.editor.element:
            i -= 1
            nodeStack.append(node)
            node = node.parentNode

        if "TABLE" in [(x.tagName if "tagName" in dir(x) else "")
                       for x in nodeStack]:
            self.tableDiv["style"]["display"] = ""
        else:
            self.tableDiv["style"]["display"] = "none"

        self.linkEditor.onCursorMoved(nodeStack)
        self.imgEditor.onCursorMoved(nodeStack)
        self.cursorMovedEvent.fire(nodeStack)